Tag: Form

Don’t put pointer-events: none on form labels

Bruce Lawson with the tip of the day, warning against the use of pointer-events: none on forms labels. We know that pointer-events is used to change how elements respond to click, tap, hover, and active states. But it apparently borks form labels, squashing their active hit target size to something small and tough to interact with. Bruce includes examples in his post.

That’s not the striking part of the post though. It’s that the issue was pinned to an implementation of Material Design’s floating labels component. Bruce fortunately had pointer events expert Patrick Lauke’s ear, who pointed (get it?) out the issue.

That isn’t a dig at frameworks. It’s just the reality of things. Front-end developers gotta be aware, and that includes awareness of third-party code.

Direct Link to ArticlePermalink


The post Don’t put pointer-events: none on form labels appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,

Lightweight Form Validation with Alpine.js and Iodine.js

Many users these days expect instant feedback in form validation. How do you achieve this level of interactivity when you’re building a small static site or a server-rendered Rails or Laravel app? Alpine.js and Iodine.js are two minimal JavaScript libraries we can use to create highly interactive forms with little technical debt and a negligible hit to our page-load time. Libraries like these prevent you from having to pull in build-step heavy JavaScript tooling which can complicate your architecture.

I‘m going to iterate through a few versions of form validation to explain the APIs of these two libraries. If you want to copy and paste the finished product here‘s what we’re going to build. Try playing around with missing or invalid inputs and see how the form reacts:

A quick look at the libraries

Before we really dig in, it’s a good idea to get acquainted with the tooling we’re using.

Alpine is designed to be pulled into your project from a CDN. No build step, no bundler config, and no dependencies. It only needs a short GitHub README for its documentation. At only 8.36 kilobytes minfied and gzipped, it’s about a fifth of the size of a create-react-app hello world. Hugo Di Fracesco offers a complete and thorough overview of what it is an how it works. His initial description of it is pretty great:

Alpine.js is a Vue template-flavored replacement for jQuery and vanilla JavaScript rather than a React/Vue/Svelte/WhateverFramework competitor.

Iodine, on the other hand, is a micro form validation library, created by Matt Kingshott who works in the Laravel/Vue/Tailwind world. Iodine can be used with any front-end-framework as a form validation helper. It allows us to validate a single piece of data with multiple rules. Iodine also returns sensible error messages when validation fails. You can read more in Matt’s blog post explaining the reasoning behind Iodine.

A quick look at how Iodine works

Here’s a very basic client side form validation using Iodine. We‘ll write some vanilla JavaScript to listen for when the form is submitted, then use DOM methods to map through the inputs to check each of the input values. If it‘s incorrect, we’ll add an “invalid” class to the invalid inputs and prevent the form from submitting.

We’ll pull in Iodine from this CDN link for this example:

<script src="https://cdn.jsdelivr.net/gh/mattkingshott/iodine@3/dist/iodine.min.js" defer></script>

Or we can import it into a project with Skypack:

import kingshottIodine from "https://cdn.skypack.dev/@kingshott/iodine";

We need to import kingshottIodine when importing Iodine from Skypack. This still adds Iodine to our global/window scope. In your user code, you can continue to refer to the library as Iodine, but make sure to import kingshottIodine if you’re grabbing it from Skypack.

To check each input, we call the is method on Iodine. We pass the value of the input as the first parameter, and an array of strings as the second parameter. These strings are the rules the input needs to follow to be valid. A list of built-in rules can be found in the Iodine documentation.

Iodine’s is method either returns true if the value is valid, or a string that indicates the failed rule if the check fails. This means we‘ll need to use a strict comparison when reacting to the output of the function; otherwise, JavaScript assesses the string as true. What we can do is store an array of strings for the rules for each input as JSON in HTML data attributes. This isn’t built into either Alpine or Iodine, but I find it a nice way to co-locate inputs with their constraints. Note that if you do this you’ll need to surround the JSON with single quotes and use double quotes inside the attribute to follow the JSON spec.

Here’s how this looks in our HTML:

<input name="email" type="email" id="email" data-rules='["required","email"]'>

When we‘re mapping through the DOM to check the validity of each input, we call the Iodine function with the element‘s input value, then the JSON.encode() result of the input’s dataset.rules. This is what this looks like using vanilla JavaScript DOM methods:

let form = document.getElementById("form");  // This is a nice way of getting a list of checkable input elements // And converting them into an array so we can use map/filter/reduce functions: let inputs = [...form.querySelectorAll("input[data-rules]")];  function onSubmit(event) {   inputs.map((input) => {     if (Iodine.is(input.value, JSON.parse(input.dataset.rules)) !== true) {       event.preventDefault();       input.classList.add("invalid");     }   }); } form.addEventListener("submit", onSubmit);

Here’s what this very basic implementation looks like:

As you can tell this is not a great user experience. Most importantly, we aren’t telling the user what is wrong with the submission. The user also has to wait until the form is submitted before finding out anything is wrong. And frustratingly, all of the inputs keep the “invalid” class even after the user has corrected them to follow our validation rules.

This is where Alpine comes into play

Let’s pull it in and use it to provide nice user feedback while interacting with the form.

A good option for form validation is to validate an input when it’s blurred or on any changes after it has been blurred. This makes sure we‘re not yelling at the user before they’ve finished writing, but still give them instant feedback if they leave an invalid input or go back and correct an input value.

We’ll pull Alpine in from the CDN:

<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.3/dist/alpine.min.js" defer></script>

Or we can import it into a project with Skypack:

import alpinejs from "https://cdn.skypack.dev/alpinejs";

Now there’s only two pieces of state we need to hold for each input:

  • Whether the input has been blurred
  • The error message (the absence of this will mean we have a valid input)

The validation that we show in the form is going to be a function of these two pieces of state.

Alpine lets us hold this state in a component by declaring a plain JavaScript object in an x-data attribute on a parent element. This state can be accessed and mutated by its children elements to create interactivity. To keep our HTML clean, we can declare a JavaScript function that returns all the data and/or functions the form would need. Alpine will look for the this function in the global/window scope of our JavaScript code if we add this function to the x-data attribute. This also provides a reusable way to share logic as we can use the same function in multiple components or even multiple projects.

Let’s initialize the form data to hold objects for each input field with two properties: an empty string for the errorMessage and a boolean called blurred. We’ll use the name attribute of each element as their keys.

 <form id="form" x-data="form()" action="">   <h1>Log In</h1>    <label for="username">Username</label>   <input name="username" id="username" type="text" data-rules='["required"]'>    <label for="email">Email</label>   <input name="email" type="email" id="email" data-rules='["required","email"]'>    <label for="password">Password</label>   <input name="password" type="password" id="password" data-rules='["required","minimum:8"]'>    <label for="passwordConf">Confirm Password</label>   <input name="passwordConf" type="password" id="passwordConf" data-rules='["required","minimum:8"]'>    <input type="submit"> </form>

And here’s our function to set up the data. Note that the keys match the name attribute of our inputs:

window.form = () => {    return {     username: {errorMessage:'', blurred:false},     email: {errorMessage:'', blurred:false},     password: {errorMessage:'', blurred:false},     passwordConf: {errorMessage:'', blurred:false},   } }

Now we can use Alpine’s x-bind:class attribute on our inputs to add the “invalid” class if the input has blurred and a message exists for the element in our component data. Here’s how this looks in our username input:

<input name="username" id="username" type="text"  x-bind:class="{'invalid':username.errorMessage && username.blurred}" data-rules='["required"]'>

Responding to input changes

Now we need our form to respond to input changes and on blurring input states. We can do this by adding event listeners. Alpine gives a concise API to do this either using x-on or, similar to Vue, we can use an @ symbol. Both ways of declaring these act the same way.

On the input event we need to change the errorMessage in the component data to an error message if the value is invalid; otherwise, we’ll make it an empty string.

On the blur event we need to set the blurred property as true on the object with a key matching the name of the blurred element. We also need to recalculate the error message to make sure it doesn’t use the blank string we initialized as the error message.

So we’re going to add two more functions to our form to react to blurring and input changes, and use the name value of the event target to find what part of our component data to change. We can declare these functions as properties in the object returned by the form() function.

Here’s our HTML for the username input with the event listeners attached:

<input    name="username" id="username" type="text"   x-bind:class="{'invalid':username.errorMessage && username.blurred}"    @blur="blur" @input="input"   data-rules='["required"]' >

And our JavaScript with the functions responding to the event listeners:

window.form = () => {   return {     username: {errorMessage:'', blurred:false},     email: {errorMessage:'', blurred:false},     password:{ errorMessage:'', blurred:false},     passwordConf: {errorMessage:'', blurred:false},     blur: function(event) {       let ele = event.target;       this[ele.name].blurred = true;       let rules = JSON.parse(ele.dataset.rules)       this[ele.name].errorMessage = this.getErrorMessage(ele.value, rules);     },     input: function(event) {       let ele = event.target;       let rules = JSON.parse(ele.dataset.rules)       this[ele.name].errorMessage = this.getErrorMessage(ele.value, rules);     },     getErrorMessage: function() {     // to be completed     }   } }

Getting and showing errors

Next up, we need to write our getErrorMessage function.

If the Iodine check returns true, we‘ll set the errorMessage property to an empty string. Otherwise, we’ll pass the rule that has broken to another Iodine method: getErrorMessage. This will return a human-readable message. Here’s what this looks like:

getErrorMessage:function(value, rules){   let isValid = Iodine.is(value, rules);   if (isValid !== true) {     return Iodine.getErrorMessage(isValid);   }   return ''; }

Now we also need to show our error messages to the user.

Let’s add <p> tags with an error-message class below each input. We can use another Alpine attribute called x-show on these elements to only show them when their error message exists. The x-show attribute causes Alpine to toggle display: none; on the element based on whether a JavaScript expression resolves to true. We can use the same expression we used in the the show-invalid class on the input.

To display the text, we can connect our error message with x-text. This will automatically bind the innertext to a JavaScript expression where we can use our component state. Here’s what this looks like:

<p x-show="username.errorMessage && username.blurred" x-text="username.errorMessage" class="error-message"></p>

One last thing we can do is re-use the onsubmit code from before we pulled in Alpine, but this time we can add the event listener to the form element with @submit and use a submit function in our component data. Alpine lets us use $ el to refer to the parent element holding our component state. This means we don’t have to write lengthier DOM methods:

<form id="form" x-data="form()" @submit="submit" action="">   <!-- inputs...  --> </form>
submit: function (event) {   let inputs = [...this.$  el.querySelectorAll("input[data-rules]")];   inputs.map((input) => {     if (Iodine.is(input.value, JSON.parse(input.dataset.rules)) !== true) {       event.preventDefault();     }   }); }

This is getting there:

  • We have real-time feedback when the input is corrected.
  • Our form tells the user about any issues before they submit the form, and only after they’ve blurred the inputs.
  • Our form does not submit when there are invalid properties.

Validating on the client side of a server-side rendered app

There are still some problems with this version, though some won‘t be immediately obvious in the Pen as they‘re related to the server. For example, it‘s difficult to validate all errors on the client side in a server-side rendered app. What if the email address is already in use? Or a complicated database record needs to be checked? Our form needs to have a way to show errors found on the server. There are ways to do this with AJAX, but we’ll look at a more lightweight solution.

We can store the server side errors in another JSON array data attribute on each input. Most back-end frameworks will provide a reasonably easy way to do this. We can use another Alpine attribute called x-init to run a function when the component initializes. In this function we can pull the server-side errors from the DOM into each input’s component data. Then we can update the getErrorMessage function to check whether there are server errors and return these first. If none exist, then we can check for client-side errors.

<input name="username" id="username" type="text"  x-bind:class="{'invalid':username.errorMessage && username.blurred}"  @blur="blur" @input="input" data-rules='["required"]'  data-server-errors='["Username already in use"]'>

And to make sure the server side errors don’t show the whole time, even after the user starts correcting them, we’ll replace them with an empty array whenever their input gets changed.

Here’s what our init function looks like now:

init: function () {   this.inputElements = [...this.$  el.querySelectorAll("input[data-rules]")];   this.initDomData(); }, initDomData: function () {   this.inputElements.map((ele) => {   this[ele.name] = {     serverErrors: JSON.parse(ele.dataset.serverErrors),     blurred: false     };   }); }

Handling interdependent inputs

Some of the form inputs may depend on others for their validity. For example, a password confirmation input would depend on the password it is confirming. Or a date you started a job field would need to hold a value later than your date-of-birth field. This means it’s a good idea to check all the inputs of the form every time an input gets changed.

We can map through all of the input elements and set their state on every input and blur event. This way, we know that inputs that rely on each other will not be using stale data.

To test this out, let’s add a matchingPassword rule for our password confirmation. Iodine lets us add new custom rules with an addRule method.

Iodine.addRule(   "matchingPassword",   value => value === document.getElementById("password").value );

Now we can set a custom error message by adding a key to the messages property in Iodine:

Iodine.messages.matchingPassword="Password confirmation needs to match password";

We can add both of these calls in our init function to set up this rule.

In our previous implementation, we could have changed the “password” field and it wouldn’t have made the “password confirmation” field invalid. But now that we’re mapping through all the inputs on every change, our form will always make sure the password and the password confirmation match.

Some finishing touches

One little refactor we can do is to make the getErrorMessage function only return a message if the input has been blurred — this can make out HTML slightly shorter by only needing to check one value before deciding whether to invalidate an input. This means our x-bind attribute can be as short as this:

x-bind:class="{'invalid':username.errorMessage}"

Here’s what our functions look like to map through the inputs and set the errorMessage data now:

updateErrorMessages: function () {   // Map through the input elements and set the 'errorMessage'   this.inputElements.map((ele) => {     this[ele.name].errorMessage = this.getErrorMessage(ele);   }); }, getErrorMessage: function (ele) {   // Return any server errors if they're present   if (this[ele.name].serverErrors.length > 0) {     return input.serverErrors[0];   }   // Check using Iodine and return the error message only if the element has not been blurred   const error = Iodine.is(ele.value, JSON.parse(ele.dataset.rules));   if (error !== true && this[ele.name].blurred) {     return Iodine.getErrorMessage(error);   }   // Return empty string if there are no errors   return ""; },

We can also remove the @blur and @input events from all of our inputs by listening for these events in the parent form element. However, there is a problem with this: the blur event does not bubble (parent elements listening for this event will not be passed it when it fires on their children). Luckily, we can replace blur with the focusout event, which is basically the same event, but this one bubbles, so we can listen for it in our form parent element.

Finally, our code is growing a lot of boilerplate. If we were to change any input names we would have to rewrite the data in our function every time and add new event listeners. To prevent rewriting the component data every time, we can map through the form’s inputs that have a data-rules attribute to generate our initial component data in the init function. This makes the code more reusable for additional forms. All we’d need to do is include the JavaScript and add the rules as a data attribute and we’re good to go.

Oh, and hey, just because it’s so easy to do with Alpine, let’s add a fade-in transition that brings attention to the error messaging:

<p class="error-message" x-show.transition.in="username.errorMessage" x-text="username.errorMessage"></p>

And here’s the end result. Reactive, reusable form validation at a minimal page-load cost.

If you want to use this in your own application, you can copy the form function to reuse all the logic we’ve written. All you’d need to do is configure your HTML attributes and you’d be ready to go.


The post Lightweight Form Validation with Alpine.js and Iodine.js appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Happier HTML5 form validation in Vue

It’s kind of neat that we can do input:invalid {} in CSS to style an input when it’s in an invalid state. Yet, used exactly like that, the UX is pretty bad. Say you have <input type="text" required>. That’s immediately invalid before the user has done anything. That’s such a bummer UX that you never see it used in the wild. But if we could just avoid that one thing, that :invalid selector can do a ton of work for us in form validation without needing to lean on a big fancy library.

Dave has an idea that’s a variation on the original 2017 idea.

It’s basically:

form.errors :invalid {   outline: 2px solid red; }

Now you’re only conditionally applying those native error styles when you’ve determined the form to be in an error state and added a class. And fortunately, testing that is pretty easy too. We could apply that class when the submit button is clicked:

submitButton.addEventListener("click", (e) => {   form.classList.toggle("errors", !form.checkValidity()) });

Or you could do it when an input blurs or something. You could even wrap each input set in a wrapper and toggle the class on the wrapper when appropriate. The commented out code here could get you going there…

Dave kicked this idea over to Vue:

We initialize the form component with errors: false because we don’t want the error styling until the user has submitted the form. The invalidateForm function just sets this.error = true. That’s one problem with the CSS :invalid pseudo class, it’s way too eager. Hooking into the invalid events, delays the styling until after the first form submission attempt and we know the form has errors.

Not using any library (on top of what you already use) is pretty sweet. HTML form validation is pretty much there. Here’s a fork of Dave’s where error messaging is revealed as well:

Direct Link to ArticlePermalink


The post Happier HTML5 form validation in Vue appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Form design

A very digestable guide from Geri Reid on building forms. Not the code, but the design and UX principles that should guide the code.

Working on a design system for a bank has taught [me] a lot about forms. I’ve watched testing in our labs. I’ve worked alongside experts from specialist accessibility organisations. I’ve seen forms tested by disabled people and users of assistive technology. I’ve also read a lot of research.

From all this learning I’ve formed my own forms best-practice guidelines. 

I always think about one code-related thing when it comes to general form advice: all inputs need an attached label.

<label for="name">Name:</label> <input type="text" id="name" name="name">  <!-- or -->  <label>   Name:   <input type="text" name="name"> </label>

It’s HTML 101 stuff, but so many forms fail to do it. I once heard a story about blind college-bound high school students who were unable to apply to college specifically because they couldn’t figure out what the inputs wanted on a form. They started second-guessing if they could do college on their own after that experience.

You know how The Onion prints the article “‘No Way To Prevent This,’ Says Only Nation Where This Regularly Happens” literally every single time there is a mass shooting? I feel like someone should make a website that publishes an article pointing to every single website that fails this one test with a headline like “‘No Way To Prevent This’, Says Website Where The Fix Would Be Typing A Handful Of Characters.”

Direct Link to ArticlePermalink


The post Form design appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

,
[Top]

Chrome 83 Form Element Styles

There have been some aesthetic changes to what form elements look like as of Chrome 83. Anything with gradient colorization is gone (notably the extra-shiny <meter> stuff). The consistency across the board is nice, particularly between inputs and textareas. Not a big fan of the new <select> styling, but I hear a lot of accessibility research went into this, so it’s hard to complain there — plus you can always change it.

Hakim has a nice comparison tweet:

The Jetpack plugin for WordPress has a new comparison block and I’m going to try it out here. You can swipe between the items, just for fun (drag the slider in the middle):

This is not accompanied by new standardized ways to change the look of form elements with CSS, although browsers are well aware of that and seem to draw nearer and nearer all the time. I believe is was a step along that path.

I also see there is a new <input type="time"> as well. The old version looked like this and offered no UI controls:

Now we get this beast with controls:

There are no visual indicators or buttons, but you can scroll those columns.

Reddit notes that it uses the same pseudo element that date pickers use, so if you want it gone, you can scope it to these types of inputs (or not) and remove it.

input[type="time"]::-webkit-calendar-picker-indicator {   display: none; }

I’d call it an improvement (I like UI controls for things), but it does continue to highlight the need to be able to style these things, particularly if the goal is to have people actually use them and not (poorly) rebuild them.

The post Chrome 83 Form Element Styles appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

How to Redirect a Search Form to a Site-Scoped Google Search

This is just a tiny little trick that might be helpful on a site where you don’t have the time or desire to build out a really good on-site search solution. Google.com itself can perform searches scoped to one particular site. The trick is getting people there using that special syntax without them even knowing it.

Make a search form:

<form action="https://google.com/search" target="_blank" type="GET">    <label>      Search CSS-Tricks on Google:       <input type="search" name="q">    </label>    <input type="submit" value="Go">  </form>

When that form is submitted, we’ll intercept it and change the value to include the special syntax:

var form = document.querySelector("form");  form.addEventListener("submit", function (e) {   e.preventDefault();   var search = form.querySelector("input[type=search]");   search.value = "site:css-tricks.com " + search.value;   form.submit(); }); 

That’s all.

The post How to Redirect a Search Form to a Site-Scoped Google Search appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Better Form Inputs for Better Mobile User Experiences

Here’s one simple, practical way to make apps perform better on mobile devices: always configure HTML input fields with the correct type, inputmode, and autocomplete attributes. While these three attributes are often discussed in isolation, they make the most sense in the context of mobile user experience when you think of them as a team. 

There’s no question that forms on mobile devices can be time-consuming and tedious to fill in, but by properly configuring inputs, we can ensure that the data entry process is as seamless as possible for our users. Let’s take a look at some examples and best practices we can use to create better user experiences on mobile devices.

Use this demo to experiment on your own, if you’d like.

Using the correct input type

This is the easiest thing to get right. Input types, like email, tel, and url, are well-supported across browsers. While the benefit of using a type, like tel over the more generic text, might be hard to see on desktop browsers, it’s immediately apparent on mobile.

Choosing the appropriate type changes the keyboard that pops up on Android and iOS devices when a user focuses the field. For very little effort, just by using the right type, we will show custom keyboards for email, telephone numbers, URLs, and even search inputs

Text input type on iOS (left) and Android (right)
Email input type on iOS (left) and Android (right)
URL input type on iOS (left) and Android (right)
Search input type on iOS (left) and Android (right)

One thing to note is that both input type="email" and input type="url" come with validation functionality, and modern browsers will show an error tooltip if their values do not match the expected formats when the user submits the form. If you’d rather turn this functionality off, you can simply add the novalidate attribute to the containing form.

A quick detour into date types

HTML inputs comprise far more than specialized text inputs — you also have radio buttons, checkboxes, and so on. For the purposes of this discussion, though, I’m mostly talking about the more text-based inputs

There is a type of input that sits in the liminal space between the more free-form text inputs and input widgets like radio buttons: date. The date input type comes in a variety of flavors that are well-supported on mobile, including date, time, datetime-local, and month. These pop up custom widgets in iOS and Android when they are focused. Instead of triggering a specialized keyboard, they show a select-like interface in iOS, and various different types of widgets on Android (where the date and time selectors are particularly slick). 

I was excited to start using native defaults on mobile, until I looked around and realized that most major apps and mobile websites use custom date pickers rather than native date input types. There could be a couple reasons for this. First, I find the native iOS date selector to be less intuitive than a calendar-type widget. Second, even the beautifully-designed Android implementation is fairly limited compared to custom components — there’s no easy way to input a date range rather than a single date, for instance. 

Still, the date input types are worth checking out if the custom datepicker you’re using doesn’t perform well on mobile. If you’d like to try out the native input widgets on iOS and Android while making sure that desktop users see a custom widget instead of the default dropdown, this snippet of CSS will hide the calendar dropdown for desktop browsers that implement it:

::-webkit-calendar-picker-indicator {   display: none; }
Date input type on iOS (left) and Android (right)
Time input type on iOS (left) and Android (right)

One final thing to note is that date types cannot be overridden by the inputmode attribute, which we’ll discuss next.

Why should I care about inputmode?

The inputmode attribute allows you to override the mobile keyboard specified by the input’s type and directly declare the type of keyboard shown to the user. When I first learned about this attribute, I wasn’t impressed — why not just use the correct type in the first place? But while inputmode is often unnecessary, there are a few places where the attribute can be extremely helpful. The most notable use case that I’ve found for inputmode is building a better number input.

While some HTML5 input types, like url and email, are straightforward, input type="number" is a different matter. It has some accessibility concerns as well as a somewhat awkward UI. For example, desktop browsers, like Chrome, show tiny increment arrows that are easy to trigger accidentally by scrolling.

So here’s a pattern to memorize and use going forwards. For most numeric inputs, instead of using this: 

<input type="number" />

…you actually want to use this:

<input type="text" inputmode="decimal" />

Why not inputmode="numeric" instead of inputmode="decimal"

The numeric and decimal attribute values produce identical keyboards on Android. On iOS, however, numeric displays a keyboard that shows both numbers and punctuation, while decimal shows a focused grid of numbers that almost looks exactly like the tel input type, only without extraneous telephone-number focused options. That’s why it’s my preference for most types of number inputs.

iOS numeric input (left) and decimal input (right)
Android numeric input (left) and decimal input (right)

Christian Oliff has written an excellent article dedicated solely to the inputmode attribute.

Don’t forget autocomplete

Even more important than showing the correct mobile keyboard is showing helpful autocomplete suggestions. That can go a long way towards creating a faster and less frustrating user experience on mobile.

While browsers have heuristics for showing autocomplete fields, you cannot rely on them, and should still be sure to add the correct autocomplete attribute. For instance, in iOS Safari, I found that an input type="tel" would only show autocomplete options if I explicitly added a autocomplete="tel" attribute.

You may think that you are familiar with the basic autocomplete options, such as those that help the user fill in credit card numbers or address form fields, but I’d urge you to review them to make sure that you are aware of all of the options. The spec lists over 50 values! Did you know that autocomplete="one-time-code" can make a phone verification user flow super smooth?

Speaking of autocomplete…

I’d like to mention one final element that allows you to create your own custom autocomplete functionality: datalist. While it creates a serviceable — if somewhat basic — autocomplete experience on desktop Chrome and Safari, it shines on iOS by surfacing suggestions in a convenient row right above the keyboard, where the system autocomplete functionality usually lives. Further, it allows the user to toggle between text and select-style inputs.

On Android, on the other hand, datalist creates a more typical autocomplete dropdown, with the area above the keyboard reserved for the system’s own typeahead functionality. One possible advantage to this style is that the dropdown list is easily scrollable, creating immediate access to all possible options as soon as the field is focused. (In iOS, in order to view more than the top three matches, the user would have to trigger the select picker by pressing the down arrow icon.)

You can use this demo to play around with datalist:

And you can explore all the autocomplete options, as well as input type and inputmode values, using this tool I made to help you quickly preview various input configurations on mobile.

In summary

When I’m building a form, I’m often tempted to focus on perfecting the desktop experience while treating the mobile web as an afterthought. But while it does take a little extra work to ensure forms work well on mobile, it doesn’t have to be too difficult. Hopefully, this article has shown that with a few easy steps, you can make forms much more convenient for your users on mobile devices.

The post Better Form Inputs for Better Mobile User Experiences appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Custom Styling Form Inputs With Modern CSS Features

It’s entirely possible to build custom checkboxes, radio buttons, and toggle switches these days, while staying semantic and accessible. We don’t even need a single line of JavaScript or extra HTML elements! It’s actually gotten easier lately than it has been in the past. Let’s take a look.

Here’s where we’ll end up:

Things sure have gotten easier than they were!

The reason is that we can finally style the ::before and ::after pseudo-elements on the <input> tag itself. This means we can keep and style an <input> and won’t need any extra elements. Before, we had to rely on the likes of an extra <div> or <span>, to pull off a custom design.

Let’s look at the HTML

Nothing special here. We can style our inputs with just this HTML:

<!-- Checkbox --> <input type="checkbox">  <!-- Radio --> <input type="radio">  <!-- Switch --> <input type="checkbox" class="switch">

That’s it for the HTML part, but of course it’s recommended to have name and id attributes, plus a matching <label> element:

<!-- Checkbox --> <input type="checkbox" name="c1" id="c1"> <label for="c1">Checkbox</label>  <!-- Radio --> <input type="radio" name="r1" id="r1"> <label for="r1">Radio</label>  <!-- Switch --> <input type="checkbox" class="switch" name="s1" id="s1"> <label for="s1">Switch</label>

Getting into the styling 

First of all, we check for the support of appearance: none;, including it’s prefixed companions. The appearance property is key because it is designed to remove a browser’s default styling from an element. If the property isn’t supported, the styles won’t apply and default input styles will be shown. That’s perfectly fine and a good example of progressive enhancement at play.

@supports(-webkit-appearance: none) or (-moz-appearance: none) {   input[type='checkbox'],   input[type='radio'] {     -webkit-appearance: none;     -moz-appearance: none;   } }

As it stands today, appearance  is a working draft, but here’s what support looks like:

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 Firefox IE Edge Safari
82* 74* No 79* TP*

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
79* 68* 76* 13.3*

Like links, we’ve gotta consider different interactive states with form elements. We’ll consider these when styling our elements:

  • :checked
  • :hover
  • :focus
  • :disabled

For example, here’s how we can style our toggle input, create the knob, and account for the :checked state:

/* The toggle container */ .switch {   width: 38px;   border-radius: 11px; }  /* The toggle knob */ .switch::after {   left: 2px;   top: 2px;   border-radius: 50%;   width: 15px;   height: 15px;   background: var(--ab, var(--border));   transform: translateX(var(--x, 0)); }  /* Change color and position when checked */ .switch:checked {   --ab: var(--active-inner);   --x: 17px; }  /* Drop the opacity of the toggle knob when the input is disabled */ .switch:disabled:not(:checked)::after {   opacity: .6; }

We are using the <input> element like a container. The knob inside of the input is created with the ::after pseudo-element. Again, no more need for extra markup!

If you crack open the styles in the demo, you’ll see that we’re defining some CSS custom properties because that’s become such a nice way to manage reusable values in a stylesheet:

@supports(-webkit-appearance: none) or (-moz-appearance: none) {   input[type='checkbox'],   input[type='radio'] {     --active: #275EFE;     --active-inner: #fff;     --focus: 2px rgba(39, 94, 254, .25);     --border: #BBC1E1;     --border-hover: #275EFE;     --background: #fff;     --disabled: #F6F8FF;     --disabled-inner: #E1E6F9;   } }

But there’s another reason we’re using custom properties — they work well for updating values based on the state of the element! We won’t go into full detail here, but here’s an example how we can use custom properties for different states.

/* Default */ input[type='checkbox'], input[type='radio'] {   --active: #275EFE;   --border: #BBC1E1;   border: 1px solid var(--bc, var(--border)); }  /* Override defaults */ input[type='checkbox']:checked, input[type='radio']:checked {   --b: var(--active);   --bc: var(--active); }    /* Apply another border color on hover if not checked & not disabled */ input[type='checkbox']:not(:checked):not(:disabled):hover, input[type='radio']:not(:checked):not(:disabled):hover {   --bc: var(--border-hover); }

For accessibility, we ought to add a custom focus style. We are removing the default outline because it can’t be rounded like the rest of the things we’re styling. But a border-radius along with a box-shadow can make for a rounded style that works just like an outline.

input[type='checkbox'], input[type='radio'] {   --focus: 2px rgba(39, 94, 254, .25);   outline: none;   transition: box-shadow .2s; }  input[type='checkbox']:focus, input[type='radio']:focus {   box-shadow: 0 0 0 var(--focus); }

It’s also possible to align and style the <label> element which directly follows the <input> element in the HTML:

<input type="checkbox" name="c1" id="c1"> <label for="c1">Checkbox</label>
input[type='checkbox'] + label, input[type='radio'] + label {   display: inline-block;   vertical-align: top;   /* Additional styling */ }  input[type='checkbox']:disabled + label, input[type='radio']:disabled + label {     cursor: not-allowed; }

Here’s that demo again:

Hopefully, you’re seeing how nice it is to create custom form styles these days. It requires less markup, thanks to pseudo-elements that are directly on form inputs. It requires less fancy style switching, thanks to custom properties. And it has pretty darn good browser support, thanks to @supports.

All in all, this is a much more pleasant developer experience than we’ve had to deal with in the past!

The post Custom Styling Form Inputs With Modern CSS Features appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Demonstrating Reusable React Components in a Form

Components are the building blocks of React applications. It’s almost impossible to build a React application and not make use of components. It’s widespread to the point that some third-party packages provide you with components you can use to integrate functionality into your application.

These third-party components tend to be reusable. The difference between them and components you probably have in your application has to do with specificity.

Here’s what I mean. Say you run a company that sells polo shirts. There are two things you can do:

  1. You can produce polos that already have a design on them, or
  2. You can have the buyer choose the design they want.

Some fundamental things will be consistent, like all polos should be short-sleeved. But the user can pick variations of the shirts, such as the color and size. A short-sleeve polo would make a good React component in that case: it’s the same item with different variations.

Now let’s say you’re working on a login form. Like polo shirts, the form has consistent characteristics, but instead of size and color variations, we’d be looking at input fields, a submit button, perhaps even a lost password link. This can be componentized and implemented with variations in the inputs, buttons, links, etc.

Input Element Example

Let’s look at it from the perspective of creating an input field for your form. Here is how a typical text input will look like as a React component:

class Form extends React.Component {   this.state = {     username: ''   }      handleChange = (event) => {     this.setSate({ username: event.target.value })   }    render() {     return (       <input         name="username         type={type}         placeholder="Enter username"         onChange={this.handleChange}         value={this.state.username}       />     )   } }

To make this input element reusable in other places and projects, we’ll have to extract it into its own component. Let’s call it FormInput.

const FormInput = ({   name,   type,   placeholder,   onChange,   className,   value,   error,   children,   label,   ...props }) => {      return (     <React.Fragment>       <label htmlFor={name}>{label}</label>       <input         name={name}         type={type}         placeholder={placeholder}         onChange={onChange}         value={value}         className={className}         style={error && {border: 'solid 1px red'}}       />       { error && <p>{ error }</p>}     </React.Fragment>   ) }  FormInput.defaultProps = {   type: "text",   className: "" }  FormInput.propTypes = {   name: PropTypes.string.isRequired,   type: PropTypes.string,   placeholder: PropTypes.string.isRequired,   type: PropTypes.oneOf(['text', 'number', 'password']),   className: PropTypes.string,   value: PropTypes.any,   onChange: PropTypes.func.isRequired }

The component accepts certain props, such as the attributes that we need to make the input with valid markup, including the placeholder, value and name. We set up the input element in the render function, setting the attribute values as the props that are passed to the component. We even bind the input to a label to ensure they’re always together. You can see that we’re not making assumptions by predefining anything. The idea is to ensure that the component can be used in as many scenarios as possible.

This makes for a good component because it enforces good markup (something Brad Frost calls dumb React) which goes to show that not every component has to be some highly complex piece of functionality. Then again, if we were talking about something super basic, say a static heading, then reaching for a React component might be overkill. The possible yardstick for making something a reusable component probably ought to be when you need the same functionality in other parts of an application. There’s generally no need for a “reusable” component if that component is only used once.

We can make use of our input component in another component, the LoginPage.

class LoginPage extends React.Component {   state = {     user: {       username: "",       password: ""     },     errors: {},     submitted: false   };    handleChange = event => {     const { user } = this.state;     user[event.target.name] = event.target.value;     this.setState({ user });   };    onSubmit = () => {     const {       user: { username, password }     } = this.state;     let err = {};      if (!username) {       err.username = "Enter your username!";     }      if (password.length < 8) {       err.password = "Password must be at least 8 characters!";     }      this.setState({ errors: err }, () => {       if (Object.getOwnPropertyNames(this.state.errors).length === 0) {         this.setState({ submitted: true });       }     });   };    render() {     const {       submitted,       errors,       user: { username, password }     } = this.state;     return (       <React.Fragment>         {submitted ? (           <p>Welcome onboard, {username}!</p>         ) : (           <React.Fragment>             <h3>Login!</h3>             <FormInput               label="Username"               name="username"               type="text"               value={username}               onChange={this.handleChange}               placeholder="Enter username..."               error={errors.username}               required               className="input"             />              <FormInput               label="Password"               name="password"               type="password"               value={password}               onChange={this.handleChange}               placeholder="Enter password..."               error={errors.password}               className="input"               required             />              <Button               type="submit"               label="Submit"               className="button"               handleClick={this.onSubmit}             />           </React.Fragment>         )}       </React.Fragment>     );   } }

See how LoginPage uses the FormInput twice? We’re using it both as the text input for a username and another text input for a password. If we want to make any changes to how the input functions, we can make those changes inside the FormInput component file we created and they will be applied to every instance where the input component is used. That’s the fundamental upside to having reusable components: you don’t have to repeat yourself.

Even the errors are displayed from the FormInput component.

The onSubmit function first validates the user object which we get from the form, ensuring that it fits the structure that there is a value for username. Notice that we can even extend the input’s functionality, like we did to check that the password contains at least eight characters.

If you look at the code, you’ll see a have a Button component in there. That’s not the same as a HTML <button> element, but instead another component that takes the props that define the type of button we want (submit, reset, button), its class name, what to do on click, and the label. There are lots of other button attributes we could integrate to enforce whatever standard is needed.

const Button = (props) => (   <button     type={props.type}     className={props.className}     onClick={props.handleClick}   >     {props.label}   </button> )

Here’s our final login form when all of our components are put together.

See the Pen
Reusable Button Component
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.


Wanna give this a try yourself? Try working on a reusable <select> element. If that’s too difficult, you can start with a <textarea> element, then perhaps a checkbox before hopping over to <select>. The key idea is to make it generic. I’ll like to see what you came up with, so link up your work in the comment section!

The post Demonstrating Reusable React Components in a Form appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

A Comparison of Static Form Providers

Let’s attempt to coin a term here: “Static Form Provider.” You bring your HTML <form>, but don’t worry about the back-end processing that makes it work. There are a lot of these services out there!

Static Form Providers do all tasks like validating, storing, sending notifications, and integrating with other APIs. It’s lovely when you can delegate big responsibilities like this. The cost? Typically a monthly or annual subscription, except for a few providers and limited plans. The cost is usually less than fancier “form builders” that help you build the form itself and process it.

In this article, we are going to review some of the most popular static form providers:

Before moving forward, just a note that the information for these comparisons came from visiting the site for each product and learning about the included features. Once I got all the information, I sent an email to each provider to confirm the list of features. Some of them confirmed, some didn’t. Special thanks to Kwes, FormKeep, Formspree, FormSubmit, formX, and Netlify Forms teams for confirming.

Form building components and validation

Name Custom Components Front-End Validation Back-End Validation
Kwes
Basin
FieldGoal Unable to confirm Unable to confirm Unable to confirm
FormCarry
FormKeep
Formspree
FormSubmit
formX
Getform
Netlify Forms

Components for building a form are HTML input elements, like text inputs, textareas, checkboxes, and radio buttons. When using a static form, most providers require adding custom HTML attributes. By providing the custom URL in the form action attribute, the form gets submitted on the provider’s side where it gets stored.

If you are looking for a form builder, FormKeep has a form designer feature. That means you embed custom HTML and JavaScript files in the page, and you get a styled form. Otherwise, you have to style the form by yourself.

If you need custom components, like a date picker or card inputs, Kwes and FormKeep are the only providers with this feature. If you want to validate input fields in the browser, you might use third-party libraries or writing your code which means adding additional JavaScript code to the site. Kwes is the only provider that supports front-end validation based on the rules you set in each input component. To enable this feature, you should include additional JavaScript file, which you might do nevertheless. Other static form providers rely only on HTML5 validation.

Kwes is the only provider with back-end validation, too. The rules you set in the front end are passed to the back end side. In case when front-end validation fails, you are safe, the backend validation would work. Other providers don’t have this feature; they rely only on spam protection.

Spam protection

Name Spam Protection
Kwes Artificial intelligence
Automatic Honeypot
Blacklists
Proprietary technology
Basin Akismet
CleanTalk
reCAPTCHA
Honeypot
FieldGoal Provided, but unable to confirm what powers it
FormCarry Akismet
reCAPTCHA
FormKeep Akismet
reCAPTCHA
Honeypot
Proprietary technology
Formspree reCaptcha
Profanity filter
Automated classification
FormSubmit reCaptcha
Honeypot
formX reCAPTCHA
Honeypot
Getform Akismet
reCAPTCHA
Netlify Forms Akismet
reCAPTCHA
Honeypot

Kwes advertises a 99.6% spam block success rate with no setup required.

Once your form is ready for submissions, you might find it hard to deal with spam. That’s why spam protection is essential, especially if you want to keep your sanity and serenity. All providers provide spam protection in this way or another. Some rely on Google reCAPTCHA or Akismet, some on Honeypot techniques, and some use artificial intelligence to get the job done. It is worth noting that adding an additional step to your form, like adding reCAPTCHA might affect the conversion rates on form submissions.

Email notifications

Name Confirmations Notifications Email Routing Logic
Kwes
Basin
FieldGoal Unable to confirm Unable to confirm Unable to confirm
FormCarry
FormKeep
Formspree
FormSubmit
formX
Getform
Netlify Forms

Email confirmations are essential if you want to provide a fast response to your users. With a contact form, for example, you want to get an email for every new submission. That way, you’re able to respond to the submission quickly and efficiently.

All providers, except FieldGoal, have confirmation, notification, and email routing logic features. You could set up an email form element which would be used to send an email automatically to the user with confirmation about the submission.

Some providers have other sorts of notifications besides email, like push notifications or Slack messages, which might be handy.

White labeling

Name White Label
Kwes
Basin
FieldGoal Unable to confirm
FormCarry
FormKeep
Formspree
FormSubmit
formX
Getform
Netlify Forms

When communicating via email notifications with your clients, you might want to use your brand and style. It creates better awareness and that way you familiarize your clients with your product. All providers offer this feature, with the exception of FieldGoal, which I was unable to confirm (although it might be under paid plans).

Custom redirects

Name Custom Redirects
Kwes
Basin
FieldGoal Unable to confirm
FormCarry
FormKeep
Formspree
FormSubmit
formX
Getform
Netlify Forms

Once you have captured a response from your user, you may want to let the user continue using your site. Also, you might want to communicate to the user that the submission was received. This feature is called “custom redirect,” and every provider has this feature (with another exception for FieldGoal because I was unable to confirm). Note that this feature might not be available in a free plan and require a paid or upgraded account.

Upload storage

Name Upload Storage
Kwes 200MB per file
20GB storage
Basin 100MB per submission
FieldGoal Amazon S3
FormCarry 5MB per file
5GB storage
FormKeep 2.5G storage
Formspree 10MB per file
10GB storage
FormSubmit Included, but unconfirmed storage amounts
formX
Getform 25MB per submission
25GB storage
Netlify Forms 1GB storage

Not every static form provider provides file storage. For example, formX doesn’t provide it at all. In most cases, this feature is available under paid plans. You might want to invest additional time to find out which provider offers the best service for you. Be sure to look specifically at single file size and form submission size limitations.

Data export

Name Data Export
Kwes
Basin
FieldGoal Unable to confirm
FormCarry
FormKeep
Formspree
FormSubmit
formX
Getform
Netlify Forms

Data export is important feature if you want to use it for analysis or for import to third-party software. Most providers offers CSV and JSON exports, which are the most commonly used ones.

API access

Name API Access
Kwes
Basin
FieldGoal Unable to confirm
FormCarry
FormKeep
Formspree
FormSubmit
formX On demand
Getform
Netlify Forms

If you want to control your data submissions by building custom application or script, you might benefit from having API access. Most providers have this feature, except Getform. formX offers it, but only on demand.

Webhooks/Zapier

Name Webhooks Zapier
Kwes
Basin
FieldGoal Unable to confirm
FormCarry
FormKeep
Formspree
FormSubmit
formX
Getform
Netlify Forms

When building a custom application or a script is out of budget, you might want to use webhooks to integrate data submissions with third-party software. Zapier is one of the most commonly used services for this, and only FormSubmit doesn’t support it (though it does support webhooks).

Analytics

Name Analytics
Kwes
Basin
FieldGoal Unable to confirm
FormCarry
FormKeep
Formspree
FormSubmit
formX
Getform
Netlify Forms

Analytics for static forms is a neat feature that could offer beneficial insight into how your form is performing. It may help you understand how your users interact with it, and you may spot ways to improve the form submission experience as a result. This feature is the least supported of all other features. Only Basin, FormKeep, and formX provide it.

Plan comparison

Name Plan 1 Plan 2 Plan 3 Plan 4
Kwes Free Tier
$ 0/mo.
Build spam-protected, and validated forms quicker than ever.

1 Website
Unlimited Forms
50 Spam Blocks

Bronze Tier
$ 9/mo.
Unlimited spam blocks, more form tools, and submissions.
1 Website
Unlimited Forms
Unlimited Spam Blocks
Silver Tier
$ 29/mo.
Build more powerful forms with integrations and webhooks.
3 Websites
Unlimited Forms
Unlimited Spam Blocks
4 Users
Gold Tier
$ 79/mo.
Enjoy more team members and everything with increased limits.
10 Websites
Unlimited Forms
Unlimited Spam Blocks
11 Users
Basin Standard Tier
$ 4.17/mo. (billed annually)
Premium Tier
$ 12.50/mo. (billed annually)
FieldGoal Single Tier
1 form
$ 5/mo.
Freelancer Tier
5 forms
$ 15/mo.
Studio Tier
15 forms
$ 39/mo.
Agency Tier
50 forms
$ 79/mo.
FormCarry Baby Tier
Free
Basic Tier
$ 15/mo.
Growth Tier
$ 40/mo.
FormKeep Starter Tier
$ 4.99/mo.
Starter Pack
$ 7.50 per form per month
Freelancer Tier
$ 5.90 per form per month
Agency Tier
$ 3.30 per form per month
Formspree Free Tier
$ 0/mo.
Gold Tier
$ 10/mo.
Platform Tier
$ 40/mo.
FormSubmit Unlimited
formX Free Tier
$ 0/mo.
100 submissions max.
Starter Tier
$ 4.99/mo.
SMBs & Freelancers
$ 49.99/mo.
Business & Agencies
$ 99.99/mo.
Getform Free Tier
$ 0/mo.
Basic Tier
Perfect for small businesses
$ 7.5/mo.
$ 90 per year
Agency Tier
$ 24/mo.
$ 290 annually
Enterprise Tier
$ 57.5/mo.
$ 690 annually
Netlify Forms Level 0
$ 0
100 submissions/mo.
10MB uploads/mo.
Level 1
$ 19/mo. per site
1,000 submissions/mo.
1GB uploads/mo.
Level 2
Custom pricing and limits

Static form providers have different plans, from entirely free plans and trials, to enterprise plans for every business need. Depending on a plan, you might have different features enabled. For example, FormSubmit is the only provider that offers all of its features for free, though it doesn’t support every feature we’ve covered here. You will want to invest some time to learn about which features that are most important for you and your product or business. Then go ahead and decide on which provider is most suitable for your needs.

Wrapping up

Having a form of any kind is a must-have for a large number of sites. When you use a static site generator, you might discover that static form providers make adding forms to a site almost trivial. By following a few rules for enabling static forms, you could benefit from essential features like spam protection and email notifications.

I have been using Kwes for a while now and I can honestly tell you it is a great product that fulfills all of my needs. It has smart spam protection, an easy-to-use dashboard, and impressive validation, both on the front end and back end.

Before choosing your static form providers, be sure to put down all requirements to the paper, and then find your perfect provider.

The post A Comparison of Static Form Providers appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]