Form Validation in Under an Hour with Vuelidate

Form validation has a reputation for being tricky to implement. In this tutorial, we’ll break things down to alleviate some of that pain. Creating nice abstractions for forms is something that Vue.js excels at and Vuelidate is personally my favorite option for validations because it doesn’t require a lot of hassle. Plus, it’s really flexible, so we don’t even have to do it how I’m going to cover it here. This is just a launching point.

If you simply want to copy and paste my full working example, it’s at the end. Go ahead. I won’t tell. Then your time spent is definitely under an hour and more, like, two minutes amirite?! Ahh, the internet is a beautiful place.

You may find you need to modify the form we’re using in this post so, in that case, you can read the full thing. We’ll start with a simple case and gradually build out a more concrete example. Finally, we’ll go through how to show form errors when the user has completed the form.

Simplest case: showing the entry once you’re done with the input

First, let’s show how we’d work with Vuelidate. We’ll need to create an object called validations that will mirror the data structure of what we’re trying to capture in the form. In the simplest terms, it would look like this:

data: { name: ‘’
},
validations: { name: { required }
}

This would create an object within computed properties that we can find with $v. It looks like this in Vue DevTools:

computed properties when empty

A couple things to note here: $v is a computed property. This is great because that means it’s cached until something updates, which is a very performant way to deal with these state changes. Check out my article here if you want more background on this concept.

Another thing to note: there are two objects — one general object about all validations (there’s only one here currently) and one about the property name in specific. This is great because if we’re looking for general information about all fields, we have that information. And if we need to gather specific data, we have that too.

Let’s take a look at what happens when we start typing in that input:

random typing shown in computed properties

We can see in data that we have… well, me typing like a lunatic. But let’s check out some of these other fields. $dirty, in this case, refers to whether the form has been touched at all. We can also see that the $model field is now filled in for the name object, which mirrors what’s in data.

$error and $invalid sound the same but are actually a little different. $invalid is checking if it passes validation, but $error checks both for something that’s $invalid and whether or not it’s $dirty (whether the form has been touched yet or not). If this all seems like a lot to parse (haha get it? parse?), don’t worry, we’ll walk through many of these pieces step by step.

Installing Vuelidate and creating our first form validation

OK, so that was a very simple example. Let’s build something real out of it. We’ll bring this into our application and this time we’ll make the field required and give it a minimum length requirement. In the Vue app, we’ll first add Vuelidate:

yarn add vuelidate

Now, let’s go into the main.js file and update it as follows:

import Vue from 'vue';
import Vuelidate from "vuelidate";
import App from './App.vue';
import store from './store'; Vue.use(Vuelidate);
Vue.config.productionTip = false new Vue({ store, render: h => h(App)
}).$mount('#app')

Now, in whatever component holds the form element, let’s first import the validators we’ll need:

import { required, minLength } from 'vuelidate/lib/validators'

Then, we’ll put the data inside of a function so we can reuse the component. You likely know about that one. Next, we’ll put our name form field in an object, because typically, we’d want to capture all of the form data together.

We’ll also need to include the validations, which will mirror our data. We’ll use required again, but this time we’ll also add a key/value pair for the minimum length of the characters, minLength(x), which will look something like this:

<script>
import { required, minLength } from 'vuelidate/lib/validators' export default { data() { return { formResponses: { name: '', } } }, validations: { formResponses: { name: { required, minLength: minLength(2) }, } }
}
</script>

Next, in the template, we’ll create a label for accessibility purposes. Instead of using what’s in the data to create the relationship in v-model, we’ll use that computed property ($model) that we saw earlier in the validations object.

<template> <div id="app"> <label for="fname">Name*</label> <input id="fname" class="full" v-model="$v.formResponses.name.$model" type="text"> </div>
</template>

Finally, beneath the form input, we’ll place some text beneath the form. We can use required attached to formResponses.name to see if it evaluates correctly and whether it’s provided at all. We can also see if there’s more than the minimum length of characters. We even have a params object that will tell us the number of characters we specified. We’ll use all of this to create informative error messages for our user.

<p class="error" v-if="!$v.formResponses.name.required">this field is required</p>
<p class="error" v-if="!$v.formResponses.name.minLength">Field must have at least {{ $v.formResponses.name.$params.minLength.min }} characters.</p>

And we’ll style our error class so it’s clear at a glance that they’re errors.

.error { color: red;
}

Be a little lazy

You may have noticed in that last demo that the errors are present right away and update while typing. Personally, I don’t like to show form validations that way because I think it’s distracting and confusing. What I like to do is wait to evaluate until typing has completed. For that kind of interaction, Vue comes equipped with a modifier for v-model: v-model.lazy. This will only evaluate the two-way binding once the user has completed the task with the input.

We can now improve on our single form input like this:

<label for="fname">Name*</label>
<input id="fname" class="full" v-model.lazy="$v.formResponses.name.$model" type="text">

Creating custom validators

Vuelidate comes with a lot of validators out of the box, which is really helpful. However, there are times when we need something a little more custom. Let’s make a custom validator for a strong password, and check that it matches with Vuelidate’s sameAs validator

The first thing we’ll do is make a label attached to an input, and the input will be type="password".

<section> <label for="fpass1">Password*</label> <input id="fpass1" v-model="$v.formResponses.password1.$model" type="password">
</section>

In our data, we’ll create password1 and password2 (which we’ll use these in a moment to validate matching passwords) in our formResponses object, and import what we need from the validators.

import { required, minLength, email, sameAs } from "vuelidate/lib/validators"; export default { data() { return { formResponses: { name: null, email: null, password1: null, password2: null } }; },

Then, we’ll create our custom validator. In the code below you can see that we’re using regex for different types of evaluation. We’ll create a strongPassword method, passing in our password1, and then we can check it several ways with .test(), which works as you might expect: it has to pass true if it is passing and false if not.

validations: { formResponses: { name: { required, minLength: minLength(3) }, email: { required, email }, password1: { required, strongPassword(password1) { return ( /[a-z]/.test(password1) && // checks for a-z /[0-9]/.test(password1) && // checks for 0-9 /\W|_/.test(password1) && // checks for special char password1.length >= 6 ); } }, }

I am separating out each line so you can see what’s going on, but we could also write the whole thing as a one-liner like this:

const regex = /^[a-zA-Z0-9!@#\$%\^\&*\)\(+=._-]{6,}$/g

I prefer to break it out because it is easier to modify.

This allows us to make the error text for our validation. We can make it say whatever we like, or even take this out of a v-if and make it present on the page. Up to you!

<section> <label for="fpass1">Password*</label> <input id="fpass1" v-model="$v.formResponses.password1.$model" type="password"> <p class="error" v-if="!$v.formResponses.password1.required">this field is required</p> <p class="error" v-if="!$v.formResponses.password1.strongPassword">Strong passwords need to have a letter, a number, a special character, and be more than 8 characters long.</p>
</section>

Now we can check if the second password matches the first with Vuelidate’s sameAs method:

validations: { formResponses: { password1: { required, strongPassword(password1) { return ( /[a-z]/.test(password1) && // checks for a-z /[0-9]/.test(password1) && // checks for 0-9 /\W|_/.test(password1) && // checks for special char password1.length >= 6 ); } }, password2: { required, sameAsPassword: sameAs("password1") } }
}

And we can create our second password field:

<section> <label for="fpass2">Please re-type your Password</label> <input id="fpass2" v-model="$v.formResponses.password2.$model" type="password"> <p class="error" v-if="!$v.formResponses.password2.required">this field is required</p> <p class="error" v-if="!$v.formResponses.password2.sameAsPassword">The passwords do not match.</p>
</section>

Now you can see the whole thing in action all together:

Evaluate on completion

You can see how noisy that last example is until the form has been completed. In my opinion, a better route is to evaluate when the entire form is completed so the user isn’t interrupted in the process. Here’s how we can do that.

Remember when we looked at the computed properties $v contained? It had objects for all the individual properties, but also one for all validations as well. Inside, there were three very important values:

  • $anyDirty: if the form was touched at all or left blank
  • $invalid: if there are any errors in the form
  • $anyError: if there are any errors at all (even one), this will evaluate to true

You can use $invalid, but I prefer $anyError, because it doesn’t require us to check if it’s dirty as well.

Let’s improve on our last form. We’ll put in a submit button, and a uiState string to keep track of, well, the UI state! This is incredibly useful as we can keep track of whether we’ve attempted submission, and whether we’re ready to send what we’ve collected. We’ll also make a small style improvement: position the error on the form so that it’s not moving around to in order to show the errors.

First, let’s add a few new data properties:

data() { return { uiState: "submit not clicked", errors: false, empty: true, formResponses: { ... } }
}

Now, we’ll add in a submit button at the end of the form. The .prevent modifier at the end of the @click directive acts like preventDefault, and keeps the page from reloading:

<section> <button @click.prevent="submitForm" class="submit">Submit</button>
</section>

We’ll handle some different states in the submitForm method. We’re going to use that computed property from Vuelidate ($anyDirty) to see if the form is empty. Remember, we can gather that information from this.$v. We used the formResponses object to hold all the form responses, so what we’ll use is this.$v.formResponses.$anyDirty. We’ll map that value to our “empty” data property. We’ll also do the same with errors and we’ll change the uiState to "submit clicked":

submitForm() { this.formTouched = !this.$v.formResponses.$anyDirty; this.errors = this.$v.formResponses.$anyError; this.uiState = "submit clicked"; if (this.errors === false && this.formTouched === false) { //this is where you send the responses this.uiState = "form submitted"; }
}

If the form has no errors and it’s not empty, we’ll send the responses and change the uiState to “form submitted” as well.

Now, we can handle some states for errors and empty states as well and, finally, if the form is submitted, we’ll evaluate a success.

<section> <button @click.prevent="submitForm" class="submit">Submit</button> <p v-if="errors" class="error">The form above has errors, <br>please get your act together and resubmit </p> <p v-else-if="formTouched && uiState === 'submit clicked'" class="error">The form above is empty, <br>cmon y'all you can't submit an empty form! </p> <p v-else-if="uiState === 'form submitted'" class="success">Hooray! Your form was submitted!</p>
</section>

In this form, we’ve given each section relative positioning and added a little padding at the bottom. That will allow us to give absolute positioning to the error state, which will prevent the form from moving around.

.error { color: red; font-size: 12px; position: absolute; text-transform: uppercase;
}

There’s one last thing we need to do: now that we’ve placed the errors in the form absolutely, they’ll stack on top of each other unless we place them next to each other instead. We also want to check if the form is in the error state, which will be true only after the submit button is clicked. This can be a useful way of doing things- we won’t show the errors until the user is done with the form, which can be less invasive. It’s up to you if you’d like to do it this way or the v-model.lazy example used in previous sections.

Our previous errors looked like this:

<section> ... <p class="error" v-if="!$v.formResponses.password2.required">this field is required</p> <p class="error" v-if="!$v.formResponses.password2.sameAsPassword">The passwords do not match.</p> </section>

Now, they’ll be contained together like this:

<p v-if="errors" class="error"> <span v-if="!$v.formResponses.password1.required">this field is required.</span> <span v-if="!$v.formResponses.password1.strongPassword">Strong passwords need to have a letter, a number, a special character, and be more than 8 characters long.</span>
</p>

To make things even easier on you, there’s a library that dynamically figures out what error to display based on your validation. Super cool! If you’re doing something simple, it’s probably too much overhead, but if you have a really complex form, it might save you time 🙂

And there we have it! Our form is validated and we have both errors and empty states when we need them, but none while we’re typing.

Sincere thanks to Damian Dulisz, one of the maintainers for Vuelidate, for proofing this article.

The post Form Validation in Under an Hour with Vuelidate appeared first on CSS-Tricks.

All About mailto: Links

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.

The basic functionality

<a href="mailto:someone@yoursite.com">Email Us</a>

It works!

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.

<a href="mailto:someone@yoursite.com" target="_blank" rel="noopener noreferrer">Email Us</a>

Adding a subject and body

This is somewhat rare to see for some reason, but mailto: links can define the email subject and body content as well. They are just query parameters!

mailto:chriscoyier@gmail.com?subject=Important!&body=Hi.

Add copy and blind copy support

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.

mailto:someone@yoursite.com?cc=someoneelse@theirsite.com,another@thatsite.com,me@mysite.com&bcc=lastperson@theirsite.com

This site is awful handy

mailtolink.me will help generate email links.

Use a <form> to let people craft the email first

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.

See the Pen
Use a <form> to make an email
by Chris Coyier (@chriscoyier)
on CodePen.

People don’t like surprises

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:

<a href="mailto:chriscoyier@gmail.com">chriscoyier@gmail.com</a>

Or you could use CSS to help explain with a little emoji story:

a[href^="mailto:"]::after { content: " (&#x1f4e8;&#x2197;&#xfe0f;)";
}

If you really dislike mailto: links, there is a browser extension for you.

https://ihatemailto.com/

I dig how it doesn’t just block them, but copies the email address to your clipboard and tells you that’s what it did.

The post All About mailto: Links appeared first on CSS-Tricks.

Don’t Get Clever with Login Forms

Brad points out some UX problems with a variety of apps that are doing things a little outside of the norm when it comes to their login forms. There is already a bunch of things to get right with forms to begin with (e.g. use the right input types, label your inputs, don’t have whack password requirements, use SSL, etc.)… OMG why complicate it even more?!

A “password manager test” should be a development best practice here. Does it work cleanly with the built-in browser password manager? How about 1Password and LastPass? No? Give it some love, please and thank you.

Direct Link to ArticlePermalink

The post Don’t Get Clever with Login Forms appeared first on CSS-Tricks.

Creating an Animated Login Form for TouchID

I came across this amazing Dribbble shot by Jakub Reis a while back. It caught my eye and I knew that I just had to try recreating it in code. At that moment, I didn’t know how. I tried out a bunch of different things, and about a year later, I finally managed to make this demo.

I learned a couple of things along the way, so let me take you on a little journey of what I did to make this because you may learn a thing or two as well.

See the Pen Opening screen for a banking app by Kirill Kiyutin (@kiyutink) on CodePen.

Step 1: Split the work into parts

I watched the original GIF many times. My goal was to split the animation into small, digestible chunks and I was able to break it down like this:

I know, it looks a lot — but we can do this!

Step 2: Take the original demo apart frame-by-frame

I needed to extract as much info as I could out of the original GIF to have a good understanding of the animation, so I split it up into single frames. There actually are a lot of services that can do this for us. I used one at ezgif.com but it could have just as easily been something else. Either way, this enables us to get details such as the colors, sizes, and proportions of all the different elements we need to create.

Oh, and we still need to turn the fingerprint into an SVG. Again, there are plenty of apps that will help us here. I used Adobe Illustrator to trace the fingerprint with the pen tool to get this set of paths:

See the Pen css-t. paths by Kirill Kiyutin (@kiyutink) on CodePen.

We’ll go through the same process with the line chart that appears towards the end of the animation, so might as well keep that vector editor open. 🙂

Step 3: Implement the animations

I’ll explain how the animations work in the final pen, but you can also find some of the unsuccessful approaches I took along the way in the end of the article.

I’ll focus on the important parts here and you can refer to the demos for the full code.

Filling the fingerprint

Let’s create the HTML structure of the phone screen and the fingerprint.

<div class="demo"> <div class="demo__screen demo__screen--clickable"> <svg class="demo__fprint" viewBox="0 0 180 320"> <!-- removes-forwards and removes-backwards classes will be helpful later on --> <path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--pinkish" d="M46.1,214.3c0,0-4.7-15.6,4.1-33.3"/> <path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--purplish" d="M53.5,176.8c0,0,18.2-30.3,57.5-13.7"/> <path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--pinkish" d="M115.8,166.5c0,0,19.1,8.7,19.6,38.4"/> <!-- ... and about 20 more paths like this --> </svg> 

The styles are quite simple so far. Note that I am using Sass throughout the demo — I find that it helps keep the work clean and helps with some of the heavier lifting we need to do.

// I use a $scale variable to quickly change the scaling of the whole pen, so I can focus on the animation and decide on the size later on.
$scale: 1.65;
$purplish-color: #8742cc;
$pinkish-color: #a94a8c;
$bg-color: #372546; // The main container
.demo { background: linear-gradient(45deg, lighten($pinkish-color, 10%), lighten($purplish-color, 10%)); min-height: 100vh; display: flex; justify-content: center; align-items: center; font-size: 0; user-select: none; overflow: hidden; position: relative; // The screen that holds the login component &__screen { position: relative; background-color: $bg-color; overflow: hidden; flex-shrink: 0; &--clickable { cursor: pointer; -webkit-tap-highlight-color: transparent; } } // Styles the fingerprint SVG paths &__fprint-path { stroke-width: 2.5px; stroke-linecap: round; fill: none; stroke: white; visibility: hidden; transition: opacity 0.5s ease; &--pinkish { stroke: $pinkish-color; } &--purplish { stroke: $purplish-color; } } // Sizes positions the fingerprint SVG &__fprint { width: 180px * $scale; height: 320px * $scale; position: relative; top: 20px * $scale; overflow: visible; // This is going to serve as background to show "unfilled" paths. we're gonna remove it at the moment where the filling animation is over background-image: url('https://kiyutink.github.io/svg/fprintBackground.svg'); background-size: cover; &--no-bg { background-image: none; } }
}

Now the hard part: making the fingerprint interactive. You can read about the animation of SVG lines here. That’s the method we’ll use to fill in each individual path.

Let’s create a class that describes a path element so that it’s easier to manipulate the paths later on.

class Path { constructor(selector, index) { this.index = index; this.querySelection = document.querySelectorAll(selector)[index]; this.length = this.querySelection.getTotalLength(); this.$ = $(selector).eq(index); this.setDasharray(); this.removesForwards = this.$.hasClass('demo__fprint-path--removes-forwards'); } setDasharray() { this.$.css('stroke-dasharray', `${this.length} ${this.length + 2}`); return this; } offset(ratio) { this.$.css('stroke-dashoffset', -this.length * ratio + 1); return this; } makeVisible() { this.$.css('visibility', 'visible'); return this; }
}

The general idea is this: Create an instance of this class for each path that we have in the fingerprint, and modify them in every frame. The paths will start with an offset ratio of -1 (fully invisible) and then will increase the offset ratio (which we’ll refer to as “offset” from here on) by a constant value each frame until they get to 0 (fully visible). The filling animation will be over at this point.

If you’ve never animated anything with this frame-by-frame approach, here’s a very simple demo to help understand how this works:

See the Pen 60fps raf animation proof of concept by Kirill Kiyutin (@kiyutink) on CodePen.

We should also handle the case where the user stops tapping or pressing the mouse button. In this case, we will animate in the opposite direction (subtracting a constant value from the offset each frame until it gets to -1 again).

Let’s create the function that calculates the offset increment for every frame — this’ll be useful later on.

function getPropertyIncrement(startValue, endValue, transitionDuration) { // We animate at 60 fps const TICK_TIME = 1000 / 60; const ticksToComplete = transitionDuration / TICK_TIME; return (endValue - startValue) / ticksToComplete;
}

Now it’s time to animate! We will keep the fingerprint paths in a single array:

let fprintPaths = []; // We create an instance of Path for every existing path. // We don't want the paths to be visible at first and then // disappear after the JavaScript runs, so we set them to // be invisible in CSS. That way we can offset them first // and then make them visible.
for (let i = 0; i < $(fprintPathSelector).length; i++) { fprintPaths.push(new Path(fprintPathSelector, i)); fprintPaths[i].offset(-1).makeVisible();
}

We will go through that array for each frame in the animation, animating the paths one by one:

let fprintTick = getPropertyIncrement(0, 1, TIME_TO_FILL_FPRINT); function fprintFrame(timestamp) { // We don't want to paint if less than 1000 / 65 ms elapsed // since the last frame (because there are faster screens // out there and we want the animation to look the same on // all devices). We use 65 instead of 60 because, even on // 60 Hz screens, `requestAnimationFrame` can sometimes be called // a little sooner, which can result in a skipped frame. if (timestamp - lastRafCallTimestamp >= 1000 / 65) { lastRafCallTimestamp = timestamp; curFprintPathsOffset += fprintTick * fprintProgressionDirection; offsetAllFprintPaths(curFprintPathsOffset); } // Schedule the next frame if the animation isn't over if (curFprintPathsOffset >= -1 && curFprintPathsOffset <= 0) { isFprintAnimationInProgress = true; window.requestAnimationFrame(fprintFrame); } // The animation is over. We can schedule next animation steps else if (curFprintPathsOffset > 0) { curFprintPathsOffset = 0; offsetAllFprintPaths(curFprintPathsOffset); isFprintAnimationInProgress = false; isFprintAnimationOver = true; // Remove the background with grey paths $fprint.addClass('demo__fprint--no-bg'); // Schedule the next animation step - transforming one of the paths into a string // (this function is not implemented at this step yet, but we'll do that soon) startElasticAnimation(); // Schedule the fingerprint removal (removeFprint function will be implemented in the next section) window.requestAnimationFrame(removeFprint); } // The fingerprint is back to the original state (the user has stopped holding the mouse down) else if (curFprintPathsOffset < -1) { curFprintPathsOffset = -1; offsetAllFprintPaths(curFprintPathsOffset); isFprintAnimationInProgress = false; }
}

And we’ll attach some event listeners to the demo:

$screen.on('mousedown touchstart', function() { fprintProgressionDirection = 1; // If the animation is already in progress, // we don't schedule the next frame since it's // already scheduled in the `fprintFrame`. Also, // we obviously don't schedule it if the animation // is already over. That's why we have two separate // flags for these conditions. if (!isFprintAnimationInProgress && !isFprintAnimationOver) window.requestAnimationFrame(fprintFrame);
}) // On `mouseup` / `touchend` we flip the animation direction
$(document).on('mouseup touchend', function() { fprintProgressionDirection = -1; if (!isFprintAnimationInProgress && !isFprintAnimationOver) window.requestAnimationFrame(fprintFrame);
})

…and now we should be done with the first step! Here’s how our work looks at this step:

See the Pen css-t. step 1 by Kirill Kiyutin (@kiyutink) on CodePen.

Removing the fingerprint

This part is pretty similar to the first one, only now we have to account for the fact that some of the paths remove in one direction and the rest of them in the other. That’s why we added the --removes-forwards modifier earlier.

First, we’ll have two additional arrays: one for the paths that are removed forwards and another one for the ones that are removed backwards:

const fprintPathsFirstHalf = [];
const fprintPathsSecondHalf = []; for (let i = 0; i < $(fprintPathSelector).length; i++) { // ... if (fprintPaths[i].removesForwards) fprintPathsSecondHalf.push(fprintPaths[i]); else fprintPathsFirstHalf.push(fprintPaths[i]);
}

…and we’ll write a function that offsets them in the right direction:

function offsetFprintPathsByHalves(ratio) { fprintPathsFirstHalf.forEach(path => path.offset(ratio)); fprintPathsSecondHalf.forEach(path => path.offset(-ratio));
}

We’re also going to need a function that draws the frames:

function removeFprintFrame(timestamp) { // Drop the frame if we're faster than 65 fps if (timestamp - lastRafCallTimestamp >= 1000 / 65) { curFprintPathsOffset += fprintTick * fprintProgressionDirection; offsetFprintPathsByHalves(curFprintPathsOffset); lastRafCallTimestamp = timestamp; } // Schedule the next frame if the animation isn't over if (curFprintPathsOffset >= -1) window.requestAnimationFrame(removeFprintFrame); else { // Due to the floating point errors, the final offset might be // slightly less than -1, so if it exceeds that, we'll just // assign -1 to it and animate one more frame curFprintPathsOffset = -1; offsetAllFprintPaths(curFprintPathsOffset); }
} function removeFprint() { fprintProgressionDirection = -1; window.requestAnimationFrame(removeFprintFrame);
}

Now all that’s left is to call removeFprint when we’re done filling the fingerprint:

function fprintFrame(timestamp) { // ... else if (curFprintPathsOffset > 0) { // ... window.requestAnimationFrame(removeFprint); } // ...
}

Let’s check our work now:

See the Pen css-t. part 2 by Kirill Kiyutin (@kiyutink) on CodePen.

Animating the path ends

You can see that, as the fingerprint is almost removed, some of its paths are longer than they were in the beginning. I moved them into separate paths that start animating at the right moment. I could incorporate them into the existing paths, but it would be much harder and at 60fps would make next-to-no difference.

Let’s create them:

<path class="demo__ending-path demo__ending-path--pinkish" d="M48.4,220c-5.8,4.2-6.9,11.5-7.6,18.1c-0.8,6.7-0.9,14.9-9.9,12.4c-9.1-2.5-14.7-5.4-19.9-13.4c-3.4-5.2-0.4-12.3,2.3-17.2c3.2-5.9,6.8-13,14.5-11.6c3.5,0.6,7.7,3.4,4.5,7.1"/>
<!-- and 5 more paths like this -->

…and apply some basic styles:

&__ending-path { fill: none; stroke-width: 2.5px; stroke-dasharray: 60 1000; stroke-dashoffset: 61; stroke-linecap: round; will-change: stroke-dashoffset, stroke-dasharray, opacity; transform: translateZ(0); transition: stroke-dashoffset 1s ease, stroke-dasharray 0.5s linear, opacity 0.75s ease; &--removed { stroke-dashoffset: -130; stroke-dasharray: 5 1000; } &--transparent { opacity: 0; } &--pinkish { stroke: $pinkish-color; } &--purplish { stroke: $purplish-color; }
}

Now, we have to add the --removed modifier to flow these paths in at the right moment:

function removeFprint() { $endingPaths.addClass('demo__ending-path--removed'); setTimeout(() => { $endingPaths.addClass('demo__ending-path--transparent'); }, TIME_TO_REMOVE_FPRINT * 0.9); // ...
}

Now our work is really starting to take shape:

See the Pen css-t. part 3 by Kirill Kiyutin (@kiyutink) on CodePen.

Morphing the fingerprint

OK, I found this part to be really hard to do on my own, but it’s really easy to implement with GSAP’s morphSVG plugin.

Let’s create the invisible paths (well, a path and a line to be exact 🙂) that will be the keyframes for our string:

<line id='demo__straight-path' x1="0" y1="151.3" x2="180" y2="151.3"/>
<path class="demo__hidden-path" id='demo__arc-to-top' d="M0,148.4c62.3-13.5,122.3-13.5,180,0"/>

Then we’ll use morphSVG to transition the path in between the keyframes:

const $elasticPath = $('#demo__elastic-path'); const ELASTIC_TRANSITION_TIME_TO_STRAIGHT = 250;
const WOBBLE_TIME = 1000; function startElasticAnimation() { $elasticPath.css('stroke-dasharray', 'none'); const elasticAnimationTimeline = new TimelineLite(); elasticAnimationTimeline .to('#demo__elastic-path', ELASTIC_TRANSITION_TIME_TO_STRAIGHT / 1000, { delay: TIME_TO_REMOVE_FPRINT / 1000 * 0.7, morphSVG: '#demo__arc-to-top' }) .to('#demo__elastic-path', WOBBLE_TIME / 1000, { morphSVG: '#demo__straight-path', // I played with the easing a bit to get that "vibration" effect ease: Elastic.easeOut.config(1, 0.3) })
}

We’ll call this function inside the fprintFrame once the fingerprint is filled:

function fprintFrame(timestamp) { // ... else if (curFprintPathsOffset > 0) { // ... startElasticAnimation(); // ... } // ...
}

The outcome is this:

See the Pen css-t. part 4 by Kirill Kiyutin (@kiyutink) on CodePen.

Animating the floating bullet

For this, I used some simple straightforward CSS animations. I chose the timing functions to emulate the gravity. You can play around with the timing functions here or here.

Let’s create a div:

<div class="demo__bullet"></div>

…and apply some styles to it:

&__bullet { position: absolute; width: 4px * $scale; height: 4px * $scale; background-color: white; border-radius: 50%; top: 210px * $scale; left: 88px * $scale; opacity: 0; transition: all 0.7s cubic-bezier(0.455, 0.030, 0.515, 0.955); will-change: transform, opacity; // This will be applied after the bullet has descended, to create a transparent "aura" around it &--with-aura { box-shadow: 0 0 0 3px * $scale rgba(255, 255, 255, 0.3); } // This will be applied to make the bullet go up &--elevated { transform: translate3d(0, -250px * $scale, 0); opacity: 1; } // This will be applied to make the bullet go down &--descended { transform: translate3d(0, 30px * $scale, 0); opacity: 1; transition: all 0.6s cubic-bezier(0.285, 0.210, 0.605, 0.910); }
}

Then we tie it together by adding and removing classes based on a user’s interactions:

const DELAY_TO_BULLET_AURA = 300;
const ELEVATION_TIME = 700;
const DELAY_AFTER_ELEVATION = 700; const $bullet = $('.demo__bullet'); function elevateBullet() { $bullet.addClass('demo__bullet--elevated');
} function descendBullet() { $bullet.addClass('demo__bullet--descended').removeClass('demo__bullet--elevated'); animateBulletAura();
} function animateBulletAura() { setTimeout(() => $bullet.addClass('demo__bullet--with-aura'), DELAY_TO_BULLET_AURA);
} function animateBullet() { elevateBullet(); $screen.removeClass('demo__screen--clickable'); setTimeout(descendBullet, ELEVATION_TIME + DELAY_AFTER_ELEVATION);
}

Now, we need to call the animateBullet function:

function startElasticAnimation() { // ... animateBullet();
}

Here’s where we are at this point:

See the Pen css-t. part 5 by Kirill Kiyutin (@kiyutink) on CodePen.

Morphing the string into a graph

Now, let’s turn that string into a graph where the bullet can land. We’ll add another keyframe to the morphSVG animation.

<path class="demo__hidden-path" id='demo__curve' d="M0,140.2c13.1-10.5,34.7-17,48.5-4.1c5.5,5.2,7.6,12.1,9.2,19.2c2.4,10.5,4.3,21,7.2,31.4c2.4,8.6,4.3,19.6,10.4,26.7c4.3,5,17.7,13.4,23.1,4.8c5.9-9.4,6.8-22.5,9.7-33c4.9-17.8,13-14.6,15.7-14.6c1.8,0,9,2.3,15.4,5.4c6.2,3,11.9,7.7,17.9,11.2c7,4.1,16.5,9.2,22.8,6.6"/>

We add this keyframe into our timeline like this:

const DELAY_TO_CURVE = 350;
const ELASTIC_TRANSITION_TIME_TO_CURVED = 300; function startElasticAnimation() { // ... elasticAnimationTimeline // ... .to('#demo__elastic-path', ELASTIC_TRANSITION_TIME_TO_CURVED / 1000, { delay: DELAY_TO_CURVE / 1000, morphSVG: '#demo__curve' }) // ...
}

Here’s what we get:

See the Pen css-t. part 6 by Kirill Kiyutin (@kiyutink) on CodePen.

Exploding the particles

This is a fun animation. First, we’ll create a couple of new divs that contain the particles that explode:

<div class="demo__logo-particles"> <div class="demo__logo-particle"></div> <!-- and several more of these -->
</div>
<div class="demo__money-particles"> <div class="demo__money-particle"></div> <!-- and several more of these -->
</div>

The two explosions are practically the same with the exception of a few parameters. That’s where SCSS mixins will come in handy. We can write the function once and use it on our divs.

@mixin particlesContainer($top) { position: absolute; width: 2px * $scale; height: 2px * $scale; left: 89px * $scale; top: $top * $scale; // We'll hide the whole container to not show the particles initially opacity: 0; &--visible { opacity: 1; }
} // The $sweep parameter shows how far from the center (horizontally) the initial positions of the particles can be
@mixin particle($sweep, $time) { width: 1.5px * $scale; height: 1.5px * $scale; border-radius: 50%; background-color: white; opacity: 1; transition: all $time ease; position: absolute; will-change: transform; // Phones can't handle the particles very well 🙁 @media (max-width: 400px) { display: none; } @for $i from 1 through 30 { &:nth-child(#{$i}) { left: (random($sweep) - $sweep / 2) * $scale + px; @if random(100) > 50 { background-color: $purplish-color; } @else { background-color: $pinkish-color; } } &--exploded:nth-child(#{$i}) { transform: translate3d((random(110) - 55) * $scale + px, random(35) * $scale + px, 0); opacity: 0; } }
}

Note the comment in the code that the particles don’t perform particularly well on less powerful devices such as phones. Perhaps there’s another approach here that would solve this if anyone has ideas and wants to chime in.

Alright, let’s put the mixins to use on the elements:

&__logo-particles { @include particlesContainer(15px);
} &__logo-particle { @include particle(50, 1.7s);
} &__money-particles { @include particlesContainer(100px);
} &__money-particle { @include particle(100, 1.5s);
}

Now we add the classes to the divs at the right time in JavaScript:

const DELAY_TO_ANIMATE_MONEY_PARTICLES = 300;
const DELAY_TO_ANIMATE_LOGO_PARTICLES = 500; const $moneyParticles = $('.demo__money-particle');
const $moneyParticlesContainer = $('.demo__money-particles');
const $logoParticlesContainer = $('.demo__logo-particles');
const $logoParticles = $('.demo__logo-particle'); function animateMoneyParticles() { setTimeout(() => { $moneyParticlesContainer.addClass('demo__money-particles--visible') $moneyParticles.addClass('demo__money-particle--exploded'); }, DELAY_TO_ANIMATE_MONEY_PARTICLES); } function animateLogoParticles() { setTimeout(() => { $logoParticlesContainer.addClass('demo__logo-particles--visible') $logoParticles.addClass('demo__logo-particle--exploded'); }, DELAY_TO_ANIMATE_LOGO_PARTICLES); } function elevateBullet() { // ... animateMoneyParticles(); animateLogoParticles();
}

Here’s where we’re at:

See the Pen css-t. part 7 by Kirill Kiyutin (@kiyutink) on CodePen.

Animating the account balance

Every digit will have a few random numbers that we’ll scroll through:

<div class="demo__money"> <div class="demo__money-currency">$</div> <!-- every digit will be a div like this one --> <div class="demo__money-digit"> 1 2 3 4 5 6 7 8 1 </div> // ...
</div>

We will put different transition times on all of the digits so that the animations are staggered. We can use a SCSS loop for that:

&__money-digit { // ... // we start from 2 because the first child is the currency sign 🙂 @for $i from 2 through 6 { &:nth-child(#{$i}) { transition: transform 0.1s * $i + 0.2s ease; transition-delay: 0.3s; transform: translate3d(0, -26px * $scale * 8, 0); } &--visible:nth-child(#{$i}) { transform: none; } }
}

All that’s left is to add the CSS classes at the right time:

const $money = $('.demo__money');
const $moneyDigits = $('.demo__money-digit'); function animateMoney() { $money.addClass('demo__money--visible'); $moneyDigits.addClass('demo__money-digit--visible');
} function descendBullet() { // ... animateMoney(); // ...
}

Now sit back and marvel at our work:

See the Pen css-t. part 8 by Kirill Kiyutin (@kiyutink) on CodePen.

The rest of the animations are fairly simple and involve light CSS transitions, so I won’t get into them to keep things brief. You can see all of the final code in the completed demo.

View Demo

Some final words

  • In my early attempts I tried using CSS transitions for all of the animation work. I found it virtually impossible to control the progress and direction of the animation, so shortly I abandoned that idea and waited a month or so before starting again. In reality, if I knew back then that the Web Animations API was a thing, I would have tried to make use of it.
  • I tried making the explosion with Canvas for better performance (using this article as a reference), but I found it difficult to control the frame rate with two separate requestAnimationFrame chains. If you know how to do that, then maybe you can tell me in the comments (or write an article for CSS-Tricks 🙂).
  • After I got a first working prototype, I was really unhappy with its performance. I was hitting around 40-50fps on a PC, not to mention phones at all. I spent a lot of time optimizing the code and this article was a lot of help.
  • You can see that the graph has a gradient. I did that by declaring a gradient directly in the SVG defs block:
<defs> <linearGradient id="linear" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#8742cc"/> <stop offset="100%" stop-color="#a94a8c"/> </linearGradient>
</defs>

…and then applied it in the CSS properties:

fill: url(#linear);
stroke: url(#linear);

The whole process from start to finish — discovering the Dribbble shot and finishing the work — took me about a year. I was taking month-long breaks here and there either because I didn’t know how to approach a particular aspect or I simply didn’t have enough free time to work on it. The entire process was a really valuable experience and I learned a lot of new things along the way.

That being said, the biggest lesson to take away from this is that there’s no need to shy away from taking on an ambitious task, or feel discouraged if you don’t know how to approach it at first. The web is a big place and there is plenty of space to figure things out as you go along.

The post Creating an Animated Login Form for TouchID appeared first on CSS-Tricks.

HTML for Numeric Zip Codes

I just overheard this discussion on Twitter, kicked off by Dave.

It seems like zip codes are just numbers, right? So…

<input id="zip" name="zip" type="number">

The advantage there being able to take advantage of free validation from the browser, and triggering a more helpful number-based keyboard on mobile devices.

But Zach pointed out that type="number" is problematic for zip codes because zip codes can have leading zeros (e.g. a Boston zip code might be 02119). Filament group also has a little lib for fixing this.

This is the perfect job for inputmode, as Jeremy suggests:

<input id="zip" name="zip" type="text" inputmode="numeric" pattern="^(?(^00000(|-0000))|(\d{5}(|-\d{4})))$">

But the support is pretty bad at the time of this writing.

A couple of people mentioned trying to hijack type="tel" for it, but that has its own downsides, like rejecting properly formatted 9-digit zip codes.

So, zip codes, while they look like numbers, are probably best treated as strings. Another option here is to leave it as a text input, but force numbers with pattern, as Pamela Fox documents:

<input id="zip" name="zip" type="text" pattern="[0-9]*">

As many have pointed out in the comments, it’s worth noting that numeric patterns for zip codes are best suited for the U.S. as many the codes for many other countries contain numbers and letters.

The post HTML for Numeric Zip Codes appeared first on CSS-Tricks.

POSTing an Indeterminate Checkbox Value

There is a such thing as an indeterminate checkbox value. It’s a checkbox (<input type="checkbox">) that isn’t checked. Nor is it not checked. It’s indeterminate.

We can even select a checkbox in that state and style it with CSS!

Some curious points though:

  1. It’s only possible to set via JavaScript. There is no HTML attribute or value for it.
  2. It doesn’t POST (or GET or whatever else) or have a value. It’s like being unchecked.

So, say you had a form like this:

<form action="" method="POST" id="form"> <input name="name" type="text" value="Chris" /> <input name="vegetarian" type="checkbox" class="veg"> <input type="submit" value="Submit"> </form>

And, for whatever reason, you make that checkbox indeterminate:

let veg = document.querySelector(".veg");
veg.indeterminate = true;

If you serialize that form and take a look at what will POST, you’ll get "name=Chris". No value for the checkbox. Conversely, had you checked the checkbox in the HTML and didn’t touch it in JavaScript, you’d get "name=Chris&vegetarian=on".

Apparently, this is by design. Checkboxes are meant to be boolean, and the indeterminate value is just an aesthetic thing meant to indicate that visual “child” checkboxes are in a mixed state (some checked, some not). That’s fine. Can’t change it now without serious breakage of websites.

But say you really need to know on the server if a checkbox is in that indeterminate state. The only way I can think of is to have a buddy hidden input that you keep in sync.

<input name="vegetarian" type="checkbox" class="veg">
<input name="vegetarian-value" type="hidden" class="veg-value">
let veg = document.querySelector(".veg");
let veg_value = document.querySelector(".veg-value"); veg.indeterminate = true;
veg_value.value = "indeterminate";

I’ve set the indeterminate value of one input and I’ve set another hidden input value to "indeterminate", which I can POST. Serialized means it looks like "name=Chris&vegetarian-value=indeterminate". Good enough.

See the Pen Can you POST an intermediate value? by Chris Coyier (@chriscoyier) on CodePen.

The post POSTing an Indeterminate Checkbox Value appeared first on CSS-Tricks.

CSS Only Floated Labels with :placeholder-shown pseudo class

The floated label technique has been around for a good long while and the general idea is this: we have an text input with the placeholder attribute acting as a label. When a user types into that input, the label moves from inside the input to outside of it.

Like so:

Although I don’t see this pattern used on the web all that much, I do think it’s an interesting one! There are different approaches to it, but Nick Salloum describes a new one using a combination of :not and :placeholder-shown:

This UI technique does indeed slightly bend the definitions of label and placeholder listed above, in the sense that we’re giving the placeholder more initial importance in having to explain the input to the user, but it’s a tradeoff for a neat UI component, and one that I’m personally comfortable making.

I wonder if there are other peculiar ways :not and :placeholder-shown could be put to use.

Direct Link to Article — Permalink

The post CSS Only Floated Labels with :placeholder-shown pseudo class appeared first on CSS-Tricks.

Finger-friendly numerical inputs with `inputmode`

Forms are often a nightmare on mobile. We can make the process as pain-free as possible by reacting to context. Input fields that expect numerical values should have a numerical most) larger screens, number inputs come with an incrementer/decrementer button. It’s a useful piece of UI we get for free by default. It does, however, make this kind of input totally inappropriate for a credit card number, for example.

A default number input that displays the up and down arrows that allow users to increase and decrease numbers in the field.
The default UI for number inputs looks something like this in all desktop browsers

The spec itself makes this clear.

The type=number state is not appropriate for input that happens to only consist of numbers but isn’t strictly speaking a number. For example, it would be inappropriate for credit card numbers or US postal codes. A simple way of determining whether to use type=number is to consider whether it would make sense for the input control to have a spinbox interface (e.g., with “up” and “down” arrows). Getting a credit card number wrong by 1 in the last digit isn’t a minor mistake, it’s as wrong as getting every digit incorrect. So it would not make sense for the user to select a credit card number using “up” and “down” buttons. When a spinbox interface is not appropriate, type=text is probably the right choice (possibly with a pattern attribute).

It’s easy to hide the up and down buttons with CSS:

input[type="number"] { -moz-appearance: textfield;
}
input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

It’s important to note that this isn’t the only difference between a number and text input. You should definitely follow the spec on this point! Some older browsers will strip out leading zeros for number inputs which would be a big problem for US ZIP codes. The often-useful maxlength attribute is ignored on number inputs.

Why would anybody dislike such a useful input?

The answer comes down to validation and using the input for the wrong thing. The number input performs input sanitization by default. If a user enters anything that isn’t a valid number, the value will be equal to an empty string — regardless of what the user can see on the screen.

This input sanitization can trip developers up, and there’s no way to turn it off. If you want to allow input that isn’t a valid number, don’t use type="number".

A screenshot showing a number input that is filled with numbers and includes dashes between some of the numbers.
Number input in Chrome. This might be valid input for your use case, but it’s illegitimate in the eyes of the number input.
var numberinput = document.querySelector('input[type="number"]')
numberinput.value // will be ""

This might not be what you would intuitively expect. However, if you follow the spec and only use the number input for what its designed for — actual numbers — this behavior is unproblematic.

Number Input Alternatives

iOS Solution: Use the `pattern` Attribute on a Text Input

On iOS devices, using the pattern attribute with a value of [0-9]* will bring up the numeric keypad. This only works with this exact pattern — you can’t allow any extra characters.

<label for="creditcard">credit card number:</label> <input pattern="[0-9]*" type="text" name="creditcard">
The iOS number keyboard that displays only numbers one through ten and no other special characters.
iOS with pattern attribute

Bear in mind that an iPhone won’t let the user switch keyboard type when this keyboard is displayed. Make sure these are the only keys they need to fill in the input correctly.

If you want to bring up a keypad of large numeric keys on iOS, you need to use the pattern attribute even on number inputs. Otherwise, you’ll get small and finger-friendly buttons:

A screenshot of the iOS keyboard displaying both numbers and letters.
iOS keypad for <input type="number">

A Better Solution: `inputmode`

inputmode has been a WHATWG spec for a couple of years, and has finally been implemented by Chrome as of version 66:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Opera Firefox IE Edge Safari
66 No 20 No No No

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
No No No No No No

For the widest support possible, it can be combined with the pattern attribute for iOS:

<label for="creditcard">credit card number:</label> <input inputmode="numeric" pattern="[0-9]*" type="text" name="creditcard">

This gives developers full control of the mobile UI without any extra baggage. It makes the UI finger-friendly while being more versatile than the pattern attribute as we can allow any characters we like. It controls one thing — and one thing only. inputmode is a great solution for those cases when it’s inappropriate to use type="number".

Some people would go further and ditch type="number" altogether once inputmode has better support. I’m not convinced that’s wise, but type="number" can be problematic.

if (numberInput.validity.valueMissing) {
errorMessage.textContent = "field must not be empty"
}
A screenshot of a number input filled with numbers and special characters with an error message below it that informs the user the field is empty.
Contrary to the human eye, the field is empty…

If you want to explicitly warn of empty number inputs, you’ll need to use:

if (numberInput.validity.valueMissing && !numberInput.validity.badInput) {
errorMessage.textContent = "field must not be empty"
}

According to Google, users abandon purchases twice as often on mobile as compared to desktop. Sales on phones account for only one third of all completed online purchases. Clearly people don’t tolerate fumbling through badly designed forms and jabbing at tiny inputs. Data entry needs to be effortless. While browser support is currently low, we’re only really waiting on mobile browsers. Desktop support is largely irrelevant. The input elements introduced by HTML5 are great, but they miss some edge cases. This can fill in some gaps.

The post Finger-friendly numerical inputs with `inputmode` appeared first on CSS-Tricks.

Boilerform: A Follow-Up

When Chris wrote his idea for a Boilerform, I had already been thinking about starting a new project. I’d just decided to put my front-end boilerplate to bed, and wanted something new to think about. Chris’ idea struck a chord with me immediately, so I got enthusiastically involved in the comments like an excitable puppy. That excitement led me to go ahead and build out the initial version of Boilerform, which you can check out here.

The reason for my initial excitement was that I have a guilty pleasure for forms. In various jobs, I’ve worked with forms at a pretty intense level and have learned a lot about them. This has ranged from building dynamic form builders to high-level spam protection for a Harley-Davidson® website platform. Each different project has given me a look at the front-end and back-end of the process. Each of these projects has also picked away at my tolerance for quick, lazy implementations of forms, because I’ve seen the drastic implementations of this at scale.

But hey, we’re not bad people. Forms are a nightmare to work with. Although better now: each browser treats them slightly differently. For example, check out these select menus from a selection of browsers and OSs. Not one of them looks the same.

These are just the tip of the inconsistency iceberg.

Because of these inconsistencies, it’s easy to see why developers bail out of digging too deep or just spin up a copy of Bootstrap and be done with it. Also, in my experience, the design of minor forms, such as a contact form are left until later in the project when most of the positive momentum has already gone. I’ve even been guilty of building contact forms a day before a website’s launch. 😬

There’s clearly an opportunity to make the process of working with forms—on the front-end, at least—better and I couldn’t resist the temptation to make it!

The Planning

I sat and thought about what pain-points there are when working with forms and what annoys me as a user of forms. I decided that as a developer, I hate styling forms. As a user, poorly implemented form fields annoy me.

An example of the latter is email fields. Now, if you try to fill in an email field on an iOS device, you get that annoying trait of the first letter being capitalized by the browser, because it treats it like a sentence. All you have to do to stop that behaviour is add autocapitalize="none" to your field and this stops. I know this isn’t commonly known because I rarely see it in place, but it’s such a quick win to have a positive impact on your users.

I wanted to bake these little tricks right into Boilerform to help developers make a user’s life easier. Creating a front-end boilerplate or framework is about so much more than styling and aesthetics. It’s about sharing your gained experience with others to make the landscape better as a whole.

The Specification

I needed to think about what I wanted Boilerform to do as a minimum viable product, at initial launch. I came up with the following rules:

  • It had to be compatible with most front-ends
  • It had to be well documented
  • It had to be lightweight
  • Someone should be able to drop a CDN link to their <head> and have it just work
  • Someone should also be able to expand on the source for their own projects
  • It shouldn’t be too opinionated

To achieve these points, I had some technology decisions to make. I decided to go for a low barrier-to-entry setup. This was:

  • Sass powered CSS
  • BEM
  • Plain ol’ HTML
  • A basic compilation setup

I also focused my attention on samples. CodePen was the natural fit for this because they embed really well. Users can also fork them and play with them themselves.

The last decision was to roll out a pattern library to break up components into little pieces. This helped me in a couple of ways. It helped with organization mainly—but it also helped me build Boilerform in a bitty, sporadic nature as I was working on it in the evenings.

I had my plan and my stack, so got cracking.

Keeping it simple

It’s easy for a project like this to get out of hand, so it’s useful to create some points about what Boilerform will be and also what it won’t be.

What Boilerform will be:

  • It’ll always be a boilerplate to get you off to a good start with your project
  • It’ll provide high-level help with HTML, CSS and JavaScript to make both developers’ and users’ lives easier
  • It’ll aim to be super lightweight, so it doesn’t become a heavy burden
  • It’ll offer configurable options that make it flexible and easy to mould into most web projects

What Boilerform won’t be:

  • It won’t be a silver bullet for your forms—it’ll still need some work
  • It won’t be a framework like Bootstrap or Foundation, because it’ll always be a starting point
  • It won’t be overly opinionated with its CSS and JavaScript
  • It’ll never be aimed at one particular framework or web technology

The Specifics

I know y’all like to dive in to the specifics of how things work, so let me give you a whistle-stop tour!

Namespacing the CSS

The first thing I got sorted was namespacing. I’ve worked on a multitude of different sites and setups and they all share something when it comes to CSS: conflicts. With this in mind, I wrote a @mixin that wrapped all the CSS in a .boilerform namespace.

// Source Sass
.c-button { @include namespace() { background: gray; }
} // This compiles to this with Sass: .boilerform .c-button { background: gray; }

The mixin is basic right now, but it gives us flexibility to scale. If we wanted to make the namespacing optional down-the-line, we only have to update this mixin. I love that sort of modularity.

Right now, what it does give us is safety. Nothing leaks out of Boilerform and hopefully, whatever leaks in will be handled by the namespaced resets and rules.

BEM With a Garnish of Prefixes

I love BEM. It’s been core to my CSS and markup for a few years now. One thing I love about BEM is that it helps you build small, encapsulated components. This is perfect for a project like Boilerform.

I could probably target naked elements safely because of the namespacing, but BEM is about more than just putting classes on everything. It gives me and others the freedom to write whatever markup structure we want. It’s also really easy for someone to pickup the code and understand what’s related to what, in both HTML and CSS.

Another thing I added to this setup was a component prefix. Instead of an .input-field component, we’ve got a .c-input-field component. I hope little things like that will help a new contributor see what’s a component right off the bat.

Horror Inputs Get Some Cool Styling

As mentioned above, select menus are awful to style. So are radio buttons and checkboxes.

A trick I’ve been using for a while now is abstracting the styling to other friendlier HTML elements. For example, with <select> elements, I wrap them in a .c-select-field component and use siblings to add a consistent caret.

For checkboxes and radio buttons, I visually-hide the main input and use adjacent <label> elements to display state change. Using this approach makes working with these controls so much easier. Importantly, we maintain accessibility and native events too.

Base Attributes to Make Fields Easier to Use

I touched on it above with my example about email fields and capitalization, but that wasn’t the only addition of useful attributes.

  • Search fields have autocorrect="off" on them to prevent browsers trying to fix spelling. I strongly recommend that you add this to inputs that a user inserts their name into as well.
  • Number fields have min, max and step attributes set to help with validation. It’s also great for keyboard users.
  • All fields have blank name and id attributes to hopefully speed up the wiring-up process

I’m certainly keen for this to be expanded on, because little tweaks like this are great for user experience.

Going Forward. Can You Help?

Boilerform is in a good place right now, but it has real potential to be useful. Some ideas I’ve had for its ongoing development are:

  • Introducing multiple JavaScript library integrations, such as React, Vue, and Angular
  • Create some base form layouts in the pattern library
  • Create Sass mixins for styling pesky stuff like placeholders
  • Improve configurability
  • Add new elements such as the range input
  • Create multilingual documentation

As you can see, that’s a lot of work, so it would be awesome if we can get some contributors into the project to make something truly useful for our community. Pulling in contributors with different areas of expertise and backgrounds will help us make it useful for as many people as possible, from end-users to back-end developers.

Let’s make something great together. 🙂

Check out the project site or the GitHub repository.


Boilerform: A Follow-Up is a post from CSS-Tricks

Text Input with Expanding Bottom Border

Petr Gazarov published a pretty rad little design pattern in his article Text input highlight, TripAdvisor style.

Typing animation in which a yellow border matches the length of the text inside.

It’s a trick! You can’t really make an <input> stretch like that, so Petr makes a <span> to sync the value too, which acts as the border itself. The whole thing is a React component.

If you’re willing to use a <span contenteditable> instead, you could do the whole thing in CSS!

See the Pen Outline bottom by Chris Coyier (@chriscoyier) on CodePen.

Although that also means no placeholder.

Direct Link to Article — Permalink


Text Input with Expanding Bottom Border is a post from CSS-Tricks