Formulários HTML permitem que você colete informações dos visitantes da sua página web. Listas de e-mails, formulários de contato, e comentários em postagens de blogs são exemplos comuns para páginas web pequenas, mas em organizações que dependem da sua página web para obtenção de receita, os formulários são sagrados e reverenciados.

Examples of text inputs, textareas, radio buttons, checkboxes, and other HTML form elements

Formulários são as “páginas do dinheiro”. São através deles que páginas tipo e-comércio vendem os seus produtos, como companhias que desenvolvem SaaS recebem os pagamentos por seus serviços, e como grupos sem fins lucrativos conseguem levantar dinheiro on-line. Muitas empresas medem o sucesso da sua página web pela efetividade dos seus formulários porque eles respondem questões como “quantas confirmações foram enviadas para nossa equipe de vendas?” e “quantas pessoas assinaram para receber informações sobre nossos produtos na última semana?”. Isso normalmente significa que os formulários são temas de testes A/B infinitos e para otimização.

Diagram: frontend form elements sending input to backend server for processing

Existem dois aspectos de um formulário HTML funcional: a interface com o usuário (frontend) e a comunicação com o servidor (backend). O primeiro é responsável pela aparência do formalário (conforme for definido pela HTML e CSS), enquanto o segundo é o código que processa as informações (armazenando dados em um banco de dados, enviando e-mail, etc). Vamos focar inteiramente no frontend neste capítulo, deixando o processamento do formulário no backend para um tutorial futuro.

Configuração

Infelizmente, realmente não há como contornar o fato de que a estilização dos formulários é difícil. É sempre uma boa ideia ter um modelo que represente a página exatamente como você deseja construir antes de você começar a programar, e isso é particularmente verdade para formulário. Então, aqui está o exemplo que criaremos neste capítulo:

Mobile and desktop web page mockups with several form elements

Como você pode ver, essa é um formulário de submissão para palestra para uma conferência de mentirinha. Ela contempla muitos campos de elementos HTML para criar o formulário: muitos tipos de campos de texto, um grupo de botões rádio, um menu deslizante, uma caixa de seleção e um botão de submissão.

Crie um novo projeto Atom chamado forms e um novo arquivo HTML chamado speaker-submission.html. Para começar, vamos adicionar algumas marcações como o cabeçalho (<header>). Veja! Temos alguma semantica HTML!)

<!DOCTYPE html>
<html lang='en'>
  <head>
    <meta charset='UTF-8'/>
    <title>Speaker Submission</title>
    <link rel='stylesheet' href='styles.css'/>
  </head>
  <body>
    <header class='speaker-form-header'>
      <h1>Speaker Submission</h1>
      <p><em>Want to speak at our fake conference? Fill out
        this form.</em></p>
    </header>
  </body>
</html>

Próximo, crie um arquivo styles.css e adicione a seguinte CSS. Usamos uma técnica simples de flexbox para centralizar o cabeçalho (e o formulário) não importa quanto largo a janela do navegador seja:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  color: #5D6063;
  background-color: #EAEDF0;
  font-family: "Helvetica", "Arial", sans-serif;
  font-size: 16px;
  line-height: 1.3;

  display: flex;
  flex-direction: column;
  align-items: center;
}

.speaker-form-header {
  text-align: center;
  background-color: #F6F7F8;
  border: 1px solid #D6D9DC;
  border-radius: 3px;
  
  width: 80%;
  margin: 40px 0;
  padding: 50px;
}

.speaker-form-header h1 {
  font-size: 30px;
  margin-bottom: 20px;
}

Note que estamos aderindo a abordagem de desenvolvimento primeiro para dispositivos móveis que discutimos no capítulo de Design Responsivo. Essas regras CSS básicas proveem um layout para dispositivos móveis uma base para o layout para desktop, também. Criaremos uma media query para um layout desktop com largura fixa posteriormente nesse capítulo.

Formulários HTML

Vamos aos formulários! Todo formulário na HTML inicia com o elemento, apropriado, <form>. Ele aceita uma série de atributos, mas os mais importantes são action e method. Vamos lá, adicione um formulário vazio ao nosso documento HTML, bem abaixo do <header>:

<form action='' method='get' class='speaker-form'>
</form>

O atributo action define a URL que processará o formulário. É para onde a entrada de dados capturada pelo formulário será enviada quando o usuário clicar no botão Enviar. Isso é tipicamente uma URL especial definida pelo nosso servidor web que sabe como processar o dado. Algumas tecnologias (backend) para processar os formulários são: Node.js, PHP, and Ruby on Rails, mas novamente, vamos focar somente no frontend nesse capítulo.

Diagram: <form> action and method attributes sent to backend server

O atributo method pode ser tanto um post ou um get (é interessante ressaltar que esses são métodos nativos do protocolo HTTP, por isso eles não receberão tradução neste tutorial), ambos definem como o formulário será enviado para o servidor no backend. Isso é amplamente dependente de como seu servidor quer tratar o formulário, mas a regra geral é utilizar o post quando você está alterando dados no servidor, e reservando o get para somente quando você estiver realmente pegando dados.

AO deixar o atributo action em branco, estamos falando ao formulário para enviar os dados para a mesma URL. Combinado com o método get, isso vai permitir que nós inspecionemos o conteúdo do formulário.

Estilizando Formulários

É claro, estamos olhando para um formulário em branco nesse momento, mas isso não significa que não podemos adicionar algum estilo a ele como se tivéssemos um contêiner <div>. Isso irá transformá-lo em uma caixa como o nosso elemento <header>:

.speaker-form {
  background-color: #F6F7F8;
  border: 1px solid #D6D9DC;
  border-radius: 3px;
  
  width: 80%;
  padding: 50px;
  margin: 0 0 40px 0;
}

Campos de Entrada de Texto

Para coletar dados de entrada do usuário, vamos precisar de uma nova ferramenta: o elemento <input/>. Insira o seguinte código dentro do elemento <form> para criar um campo de texto:

<div class='form-row'>
  <label for='full-name'>Name</label>
  <input id='full-name' name='full-name' type='text'/>
</div>

Primeiro, nós temos um contêiner <div> para ajudar com a estilização. Isso é muito comum para separar elementos de entrada de dados. Segundo, nós temos uma <label>, que você pode pensar nela como outro elemento semântico da HTML, como <article> ou <figcaption>, mas para rótulos. Um rótulo com o atributo for deve casar com o atributo id associado ao elemento <input/> definido.

Diagram: for attribute of <label> pointing to id attribute of <input> element

E por fim, o elemento <input/> cria um campo de texto. Isso é um pouco diferente de outros elementos que aprendemos até o momento, porque ele pode mudar drasticamente a aparência dependendo do atributo type, mas ele sempre cria algum tipo de interatividade na entrada do usuário. Nós vamos ver outros valores além do text no decorrer do capítulo. Lembre-se que seletores ID selectors são ruins—o atributo id aqui é utilizado somente para referenciar o elemento <label>.

Diagram: name attribute of <input> element sending variable to server

Conceitualmente, um elemento <input/> representa uma “variável” que é enviada para o servidor. O atributo name define o nome dessa variável, e o valor é qualquer coisa que o usuário inseriu dentro do campo de texto. Note que você pode pré-popular esse valor adicionando um atributo value a um elemento <input/>.

Estilizando campos de entrada de texto

Um elemento <input/> pode ser estilizado como qualquer outro elemento HTML. Vamos adicionar algumas regras CSS ao nosso styles.css para embelezar um pouco o formulário. Aqui vamos utilizar todos os conceitos aprendidos nos capítulos Olá, CSS, Modelo de Caixa, Seletores CSS, e Flexbox:

.form-row {
  margin-bottom: 40px;
  display: flex;
  justify-content: flex-start;
  flex-direction: column;
  flex-wrap: wrap;
}

.form-row input[type='text'] {
  background-color: #FFFFFF;
  border: 1px solid #D6D9DC;
  border-radius: 3px;
  width: 100%;
  padding: 7px;
  font-size: 14px;
}

.form-row label {
  margin-bottom: 15px;
}

A parte do código input[type='text'] é um novo tipo de seletor CSS chamado “seletor de atributo”. Ele só combina com elementos <input/> que tem um atributo type igual ao text. Isso permite que a gente selecione especificamente campos de texto, ao contrário de botões radio, que são definidos pelo mesmo elemento HTML (<input type='radio'/>). Você pode ler mais sobre seletores de atributos na Mozilla Developer Network.

Toda nossa estilização está com uma identificação (“namespaced”) em um seletor descendente chamado .form-row. Isolando o <input/> e o <label> dessa forma torna mais fácil criar diferentes tipos de formulários. Veremos por que é conveniente evitar seletores globais como input[type='text'] e label quando formos utilizar botões radio.

E finalmente, vamos hackear esse estilo base agora para criar nosso layout para desktop. Adicione a seguinte media query ao final da nossa folha de estilo.

@media only screen and (min-width: 700px) {
  .speaker-form-header,
  .speaker-form {
    width: 600px;
  }
  .form-row {
    flex-direction: row;
    align-items: flex-start; /* Para evitar que ele expanda verticalmente */
    margin-bottom: 20px;
  }
  .form-row input[type='text'] {
    width: 250px;
    height: initial;
  }
  .form-row label {
    text-align: right;
    width: 120px;
    margin-top: 7px;
    padding-right: 20px;
  }
}

Veja só esse uso incrível da propriedade flex-direction e como utilizamos ela para fazer o <label> aparecer em cima do seu elemento <input/> no layout para dispositivos móveis, mas ficar a esquerda dele no layout para desktop.

Web page showing single text field styled with CSS

Campo de entrada de E-mail

O atributo type do elemento <input/> também permite que você faça alguma validação básica da entrada de dados. Por exemplo, vamos tentar adicionar outro elemento de entrada que só aceita endereços de e-mails ao invés de qualquer tipo de texto inserido:

<div class='form-row'>
  <label for='email'>E-mail</label>
  <input id='email'
         name='email'
         type='email'
         placeholder='joe@example.com'/>
</div>

Isso funciona exatamente como a entrada type='text', exceto que ele automaticamente verificar se o usuário inseriu um endereço de e-mail. No Firefox, você pode tentar escrever alguma coisa que não é um endereço de e-mail, depois clicar fora do campo para ele perder o foco e ele vai validar seu campo. Ele deve ficar vermelho para mostrar para o usuário que esse é um valor inválido. O Chrome e o Safari não tentarão validar até que o usuário tente enviar o formulário, então nós veremos essa ação mais tarde nesse capítulo.

Web page showing invalid email field value highlighted with red border

Isso é muito mais do que uma validação, na verdade. Ao dizer ao navegador que estamos esperando um endereço de e-mail, eles podem transformar a experiência do usuário em uma forma mais intuitiva. Por exemplo, quando um navegador de smartphone vê esse atributo type='email', ele mostra para o usuário um teclado diferente, especial para inserir e-mails com o caractere @ de forma mais acessível.

Também se atente ao novo atributo placeholder que permite que você mostre um texto padrão quando o elemento <input/>está vazio. Isso é uma técnica bem legal de UX para mostrar ao usuário o valor do campo de entrada.

Existem um monte de outras validações embutidas nas opções além do endereço de e-mail, que você pode consultar na documentação da MDN <input/>. Alguns atributos em particular como o required, minlength, maxlength, e pattern.

Estilizando Campos de Entrada de E-mail

Nós queremos que nosso campo de e-mail case com nosso campo de texto da seção anterior, então vamos adicionar outro seletor de atributo a regra input[type='text'], como segue:

/* Change this rule */
.form-row input[type='text'] {
  background-color: #FFFFFF;
  /* ... */
}

/* To have another selector */
.form-row input[type='text'],
.form-row input[type='email'] {
  background-color: #FFFFFF;
  /* ... */
}

Again, we don’t want to use a plain old input type selector here because that would style all of our <input/> elements, including our upcoming radio buttons and checkbox. This is part of what makes styling forms tricky. Understanding the CSS to pluck out exactly the elements you want is a crucial skill.

Let’s not forget about our desktop styles. Update the corresponding input[type='text'] rule in our media query to match the following (note that we’re preparing for the next few sections with the select, and textarea selectors):

@media only screen and (min-width: 700px) {
  /* ... */
  .form-row input[type='text'],
  .form-row input[type='email'],    /* Add */
  .form-row select,                 /* These */
  .form-row textarea {              /* Selectors */
    width: 250px;
    height: initial;
  }
  /* ... */
}

Since we can now have a “right” and a “wrong” input value, we should probably convey that to users. The :invalid and :valid pseudo-classes let us style these states independently. For example, maybe we want to render both the border and the text with a custom shade of red when the user entered an unacceptable value. Add the following rule to our stylesheet, outside of the media query:

.form-row input[type='text']:invalid,
.form-row input[type='email']:invalid {
  border: 1px solid #D55C5F;
  color: #D55C5F;
  box-shadow: none; /* Remove default red glow in Firefox */
}

Until we include a submit button, you’ll only be able to see this in Firefox, but you get the idea. There’s a similar pseudo-class called :focus that selects the element the user is currently filling out. This gives you a lot of control over the appearance of your forms.

Radio Buttons

Changing the type property of the <input/> element to radio transforms it into a radio button. Radio buttons are a little more complex to work with than text fields because they always operate in groups, allowing the user to choose one out of many predefined options.

Diagram: <fieldset> wrapping a <legend> and a series of radio buttons with associated <label> elements

This means that we not only need a label for each <input/> element, but also a way to group radio buttons and label the entire group. This is what the <fieldset> and <legend> elements are for. Every radio button group you create should:

  • Be wrapped in a <fieldset>, which is labeled with a <legend>.
  • Associate a <label> element with each radio button.
  • Use the same name attribute for each radio button in the group.
  • Use different value attributes for each radio button.

Our radio button example has all of these components. Add the following to our <form> element underneath the email field:

<fieldset class='legacy-form-row'>
  <legend>Type of Talk</legend>
  <input id='talk-type-1'
         name='talk-type'
         type='radio'
         value='main-stage' />
  <label for='talk-type-1' class='radio-label'>Main Stage</label>
  <input id='talk-type-2'
         name='talk-type'
         type='radio'
         value='workshop'
         checked />
  <label for='talk-type-2' class='radio-label'>Workshop</label>
</fieldset>

Unlike text fields, the user can’t enter custom values into a radio button, which is why each one of them needs an explicit value attribute. This is the value that will get sent to the server when the user submits the form. It’s also very important that each radio button has the same name attribute, otherwise the form wouldn’t know they were part of the same group.

We also introduced a new attribute called checked. This is a “boolean attribute”, meaning that it never takes a value—it either exists or doesn’t exist on an <input/> element. If it does exist on either a radio button or a checkbox element, that element will be selected/checked by default.

Styling Radio Buttons

We have a few things working against us with when it comes to styling radio buttons. First, there’s simply more elements to worry about. Second, the <fieldset> and <legend> elements have rather ugly default styles, and there’s not a whole lot of consistency in these defaults across browsers. Third, at the time of this writing, <fieldset> doesn’t support flexbox.

But don’t fret! This is a good example of floats being a useful fallback for legacy/troublesome elements. You’ll notice that we didn’t wrap the radio buttons in our existing .form-row class, opting instead for a new .legacy-form-row class. This is because it’s going to be completely separate from our other elements, using floats instead of flexbox.

Diagram: mobile layout created with block box <label> versus desktop layout with label as floated left

Start with the mobile and tablet styles by adding the following rules outside of our media query. We want to get rid of the default <fieldset> and <legend> styles, then float the radio buttons and labels so they appear in one line underneath the <legend>:

.legacy-form-row {
  border: none;
  margin-bottom: 40px;
}

.legacy-form-row legend {
  margin-bottom: 15px;
}

.legacy-form-row .radio-label {
  display: block;
  font-size: 14px;
  padding: 0 20px 0 10px;
}

.legacy-form-row input[type='radio'] {
  margin-top: 2px;
}
  
.legacy-form-row .radio-label,
.legacy-form-row input[type='radio'] {
  float: left;
}

For the desktop layout, we need to make the <legend> line up with the <label> elements in the previous section (hence the width: 120px line), and we need to float everything to the left so they appear on the same line. Update our media query to include the following:

@media only screen and (min-width: 700px) {
  /* ... */
  .legacy-form-row {
    margin-bottom: 10px;
  }
  .legacy-form-row legend {
    width: 120px;
    text-align: right;
    padding-right: 20px;
  }
  .legacy-form-row legend {
    float: left;
  }
}

As far as layouts go, this is a pretty good cross-browser solution. However, customizing the appearance of the actual button is another story. It’s possible by taking advantage of the checked attribute, but it’s a little bit complicated. We’ll leave you to Google “custom radio button CSS” and explore that rabbit hole on your own.

Select Elements
(Dropdown Menus)

Dropdown menus offer an alternative to radio buttons, as they let the user select one out of many options. The <select> element represents the dropdown menu, and it contains a bunch of <option> elements that represent each item.

<div class='form-row'>
  <label for='t-shirt'>T-Shirt Size</label>
  <select id='t-shirt' name='t-shirt'>
    <option value='xs'>Extra Small</option>
    <option value='s'>Small</option>
    <option value='m'>Medium</option>
    <option value='l'>Large</option>
  </select>
</div>

Just like our radio button <input/> elements, we have name and value attributes that get passed to the backend server. But, instead of being defined on a single element, they’re spread across the <select> and <option> elements.

Styling Select Elements

And, also just like our radio buttons, <select> elements are notoriously hard to style. However, there’s a reason for this. Dropdowns are a complex piece of interactivity, and their behavior changes significantly across devices. For instance, on an iPhone, clicking a <select> element brings up a native scrolling UI component that makes it much easier to navigate the menu.

Screenshot showing three <option> elements in the scrolling <select> menu at the bottom of an iPhone screen

It’s usually a good idea to let the browser/device determine the best way to preset a <select> element, so we’ll be keeping our CSS pretty simple. Unfortunately, even the simplest things are surprisingly hard. For instance, try changing the font size of our <select> element:

.form-row select {
  width: 100%;
  padding: 5px;
  font-size: 14px;            /* This won't work in Chrome or Safari */
}

This will work in Firefox, but not in Chrome or Safari! To sort of fix this, we can use a vendor-specific prefix for the appearance property:

.form-row select {
  width: 100%;
  padding: 5px;
  font-size: 14px;            /* This won't work in Chrome or Safari */
  -webkit-appearance: none;   /* This will make it work */
}

The -webkit prefix will only apply to Chrome and Safari (which are powered by the WebKit rendering engine), while Firefox will remain unaffected. This is effectively a hack, and even MDN says not to use this CSS property.

Style difficulties like this are a serious consideration when building a form. If you need custom styles, you may be better off using radio buttons or JavaScript UI widgets. Bootstrap Dropdowns and jQuery Selectmenu’s are common JavaScript solutions for customizing select menus. In any case, at least you now understand the problem. You can read more about <select> issues here.

Textareas

The <textarea> element creates a multi-line text field designed to collect large amounts of text from the user. They’re suitable for things like biographies, essays, and comments. Go ahead and add a <textarea> to our form, along with a little piece of instructional text:

<div class='form-row'>
  <label for='abstract'>Abstract</label>
  <textarea id='abstract' name='abstract'></textarea>
  <div class='instructions'>Describe your talk in 500 words or less</div>
</div>

Note that this isn’t self-closing like the <input/> element, so you always need a closing </textarea> tag. If you want to add any default text, it needs to go inside the tags opposed to a value attribute.

Styling Textareas

Fortunately, styling textareas is pretty straightforward. Add the following to our styles.css file (before the media query):

.form-row textarea {
  font-family: "Helvetica", "Arial", sans-serif;
  font-size: 14px;

  border: 1px solid #D6D9DC;
  border-radius: 3px;

  min-height: 200px;
  margin-bottom: 10px;
  padding: 7px;
  resize: none;
}

.form-row .instructions {
  color: #999999;
  font-size: 14px;
  margin-bottom: 30px;
}

By default, many browsers let the user resize <textarea> elements to whatever dimensions they want. We disabled this here with the resize property.

We also need a little tweak in our desktop layout. The .instructions <div> needs to be underneath the <textarea>, so let’s nudge it left by the width of the <label> column. Add the following rule to the end of our media query:

@media only screen and (min-width: 700px) {
  /* ... */
  .form-row .instructions {
    margin-left: 120px;
  }
}

Checkboxes

Checkboxes are sort of like radio buttons, but instead of selecting only one option, they let the user pick as many as they want. This simplifies things, since the browser doesn’t need to know which checkboxes are part of the same group. In other words, we don’t need a <fieldset> wrapper or shared name attributes. Add the following to the end of our form:

<div class='form-row'>
  <label class='checkbox-label' for='available'>
  <input id='available'
         name='available'
         type='checkbox'
         value='is-available'/>
  <span>I’m actually available the date of the talk</span>
  </label>
</div>

The way we used <label> here was a little different than previous sections. Instead of being a separate element, the <label> wraps its corresponding <input/> element. This is perfectly legal, and it’ll make it easier to match our desired layout. It’s still a best practice to use the for attribute.

Styling Checkboxes

For the mobile layout, all we need to do is override the margin-bottom that we put on the rest the <label> elements. Add the following to styles.css, outside of the media query:

.form-row .checkbox-label {
  margin-bottom: 0;
}

And inside the media query, we have to take that 120-pixel label column into account:

@media only screen and (min-width: 700px) {
  /* ... */
  .form-row .checkbox-label {
    margin-left: 120px;
    width: auto;
  }
}

By wrapping both the checkbox and the label text, we’re able to use a width: auto to make the entire form field be on a single line (remember that the auto width makes the box match the size of its contents).

Web page with several HTML form elements, including a checkbox

Submit Buttons

Finally, let’s finish off our form with a submit button. The <button> element represents a button that will submit its containing <form>:

<div class='form-row'>
  <button>Submit</button>
</div>

Clicking the button tells the browser to validate all of the <input/> elements in the form and submit it to the action URL if there aren’t any validation problems. So, you should now be able to type in something that’s not an email address into our email field, click the <button>, and see an error message.

Screenshot showing invalid input error message for email field

This also gives us a chance to see how the user’s input gets sent to the server. First, enter some values into all the <input/> fields, making sure the email address validates correctly. Then, click the button and inspect the resulting URL in your browser. You should see something like this:


speaker-submission.html?full-name=Rick&email=rick%40internetingishard.com&talk-type=workshop&t-shirt=l&abstract=Derp.&available=is-available

Everything after the ? represents the variables in our form. Each <input/>’s name attribute is followed by an equal sign, then its value, and each variable is separated by an & character. If we had a backend server, it’d be pretty easy for it to pull out all this information, query a database (or whatever), and let us know whether the form submission was successful or not.

Styling Buttons

We had some experience styling buttons in the pseudo-classes section of the CSS Selectors chapter. Back then, we were applying these styles to an <a> element, but we can use the same techniques on a <button>.

Web page showing the form’s submit button

Clean up that ugly default <button> styling by adding the following to our stylesheet:

.form-row button {
  font-size: 16px;
  font-weight: bold;

  color: #FFFFFF;
  background-color: #5995DA;

  border: none;
  border-radius: 3px;

  padding: 10px 40px;
  cursor: pointer;
}

.form-row button:hover {
  background-color: #76AEED;
}

.form-row button:active {
  background-color: #407FC7;
}

As with our checkbox, we need to take that 120px label column into account, so include one more rule inside our media query:

@media only screen and (min-width: 700px) {
  /* ... */
  .form-row button {
    margin-left: 120px;
  }
}

Summary

In this chapter, we introduced the most common HTML form elements. We now have all these tools for collecting input from our website visitors:

  • <input type='text'/>
  • <input type='email'/>
  • <input type='radio'/>
  • <select> and <option>
  • <textarea>
  • <input type='checkbox'/>
  • <button>

You should be pretty comfortable with the HTML and CSS required to build beautiful forms, but actually making these forms functional requires some skills we don’t have yet. That stuff is out of scope for this tutorial, but it might help to have some context. Generally speaking, there are two ways to process forms:

  • Use the action attribute to send the form data to a backend URL, which then redirects to a success or error page. We got a little glimpse of this in the previous section, and it doesn’t require any JavaScript.
  • Use AJAX queries to submit the form without leaving the page. Success or error messages are displayed on the same page by manipulating the HTML with JavaScript.

Depending on how your organization is structured, form processing may not be part of your job role as a frontend web developer. If that’s the case, you’ll need to coordinate closely with a backend developer on your team to make sure the <form> submits the correct name-value pairs. Otherwise, it’ll be up to you to make sure the frontend and backend of your forms fit neatly together.

Next, we have our final chapter in HTML & CSS Is Hard. We’ll round out our frontend skills with a thorough discussion of web fonts and practical typographic principles that every web developer should know about.