Customizando widgets de formulários web

Customizando widgets de formulários web

No post anterior falamos sobre Shadow DOM e entendemos a razão pela qual não é possível estilizar widgets de formulário (selects, radio buttons, checkboxes, ranges, etc). Contudo, não é necessário esperar que a API Shadow DOM seja disponibilizada para que já possamos começar a brincar de estilizar nossos widgets, pois o CSS3 já define algumas propriedades e seletores específicos de vários componentes de formulário.

Hoje vamos descobrir como estilizar elementos de formulário e os deixarmos assim, como essa máquina da foto acima, totalmente customizada.

O CSS3 define a propriedade appearance, que nos permite fazer com que um elemento pareça com componentes de UI.

OBS.: Para visualizar os exemplos deste post corretamente, você tem que estar usando um navegador baseado em Webkit (Google Chrome ou Safari).

Show me the code

Neste primeiro exemplo vamos fazer uma coisa bem simples, mudaremos a aparência de um grupo de radio buttons:

<input type="radio" name="pinkfloyd" id="syd" />
<label for="syd">Syd Barret</label>

<input type="radio" name="pinkfloyd" id="david" />
<label for="david">David Gilmour</label>

<input type="radio" name="pinkfloyd" id="roger" />
<label for="roger">Roger Waters</label>

Este trecho de código gera os widgets abaixo:

Agora usaremos a propriedade appearance para alterar a aparência dos radio buttons. Dentre tantas opções, escolhi a que achei mais didática para este exemplo – o checkbox –, mas você pode escolher outra opção qualquer. No CSS, precisamos apenas:

input[type=radio] {
    -webkit-appearance: checkbox; /* Chrome e Safari */
    -moz-appearance: checkbox; /* Firefox */
    appearance: checkbox; /* ainda não é suportado */
}

Como podemos ver a seguir, a aparência dos radio buttons mudou para a mesma aparência das checkboxes, mas não se enganem, os widgets ainda são de fato radio buttons, podemos constatar isto pelo fato de que apenas uma das opções abaixo pode ser selecionada ao mesmo tempo. Teste você mesmo!

Customizando o input range

Show de bola! Agora, o próximo experimento será bem mais legal, desta vez iremos estilizar um slider de formulário como bem entendermos – <input type="range">, cuja aparência padrão é:

O que devemos fazer primeiramente é remover a aparência padrão setando a propriedade appearance para none, isso nos dará a liberdade de modificar qualquer propriedade do elemento, só assim poderemos começar a customização do widget. Vamos então dar continuidade com os passos iniciais no nosso widget de range de id#rolling-stones:

input#rolling-stones {
    /* removendo a aparência padrão (Chrome e Safari) */
    -webkit-appearance: none;
    /* estilização */
    width: 300px;
    height: 6px;
    border-radius: 6px;
    border: 1px solid rgba( 0,0,0,0.5 );
    box-shadow: 1px 1px 0px white;
    background-color: rgba( 0,0,0,0.1 );
    background-image: -webkit-linear-gradient( rgba(0,0,0,0.2), rgba(0,0,0,0) );
}

Após remover a aparência padrão com -webkit-appearance: none, nosso elemento vai ficar com um fundo branco, e é ai que as estilizações acima entram em cena. Mas podemos perceber que embora o fundo já tenha sido customizado, seu pequeno botão circular que desliza sobre a trilha (slider thumb) ainda permanecerá com sua aparência padrão, isso ocorre porque este botão é um outro elemento padrão encapsulado dentro do input range, e para mudarmos sua aparência padrão deveremos usar um seletor especial ::slider-thumb – que por enquanto apenas o Webkit (Chrome e Safari) dá suporte através da flag -webkit. Customizando o botão:

input#rolling-stones::-webkit-slider-thumb {
    /* removendo a aparência padrão (Chrome e Safari) */
    -webkit-appearance: none;
    /* estilização */
    width: 8px;
    height: 18px;
    border-radius: 8px;
    border: 1px solid rgba( 0,0,0,0.3 );
    box-shadow: 1px 1px 2px rgba( 0,0,0,0.2 );
    background-color: #999;
    background-image: -webkit-linear-gradient( #999, #666 );
}

A parte seguinte é mais relacionada à interação do widget com o usuário. Especificamos os comportamentos para quando há foco (:focus), para quando passamos a seta do mouse em cima do botão (:hover) e para quando clicamos e arrastamos o botão (:active):

input#rolling-stones:focus {
    outline: none;
}

input#rolling-stones:focus::-webkit-slider-thumb {
    outline: none;
    box-shadow: 0px 0px 5px 1px red;
}

input#rolling-stones::-webkit-slider-thumb:hover {
    background-image: none;
    background-color: #777;
}

input#rolling-stones::-webkit-slider-thumb:active {
    background-image: none;
    background-color: #555;
}

E eis o resultado! Recomendo testar também o focus (aperte tab e depois ← e →).

Suporte

appearance1.0
-webkit
3.0
-webkit
1.0
-moz
----
::slider-thumb20 *
-webkit
5.1 *
-webkit
------
* Suporte inicial desconhecido. Testado nessas versões apenas.

No momento em que escrevo este post, só os navegadores Mozilla Firefox, Google Chrome e Safari dão suporte (apenas parcial por enquanto) à propriedade appearance, ou seja, -moz-appearance (Firefox) e -webkit-appearance (Chrome e Safari). O problema é que cada uma dessas implementações diferem bastante uma da outra, no Firefox por exemplo, -moz-appearance pode assumir 106 valores diferentes, enquanto que no Webkit (Chrome e Safari), -webkit-appearance reconhece apenas 43 valores. Já o pseudo-elemento ::slider-thumb é suportado apenas pelo Webkit e ainda não faz parte da especificação W3C.

Customizando checkbox e radio button

Agora é a vez de fazermos o mesmo com as checkboxes e radio buttons. Abaixo temos o HTML dos nossos widgets, criei a classe .estilizado apenas para enfatizar o reaproveitamento de CSS logo a seguir.

<!-- checkboxes -->
<input class="estilizado" type="checkbox" id='c1' />
<label for="c1">Paul</label>
<input class="estilizado" type="checkbox" id='c2' />
<label for="c2">George</label>
<input class="estilizado" type="checkbox" id='c3' />
<label for="c3">John</label>
<input class="estilizado" type="checkbox" id='c4' />
<label for="c4">Ringo</label>

<!-- radio buttons -->
<input class="estilizado" type="radio" name="band" id="r1" />
<label for="r1">Motörhead</label>
<input class="estilizado" type="radio" name="band" id="r2" />
<label for="r2">AC/DC</label>
<input class="estilizado" type="radio" name="band" id="r3" />
<label for="r3">Motley Crue</label>

Abaixo o resultado padrão do código acima:

Agora vamos ao que interessa, CSS! Começamos definindo a classe .estilizado, ou seja, as propriedades inerentes tanto às checkboxes quanto aos radio buttons.

input.estilizado {
    /* removendo a aparência padrão (Chrome e Safari) */
    -webkit-appearance: none;
    /* estilização */
    border: 1px solid rgba( 0,0,0,0.5 );
    box-shadow: 1px 1px 0px white;
    background-color: white;
    background-image: -webkit-linear-gradient( rgba(0,0,0,0.2),rgba(0,0,0,0) );
}

/* quando o input estiver selecionado */
input.estilizado:checked {
    background-image: none;
    background-color: rgba( 0,0,0,0.3 );
}

/* quando o input estiver em foco */
input.estilizado:focus {
    outline: none;
    box-shadow: 0px 0px 5px 1px red;
}

Feito isso, agora vamos aos estilos adicionais das checkboxes:

input[type=checkbox].estilizado {
    width: 12px;
    height: 12px;
    border-radius: 3px;
}

/* quando o checkbox estiver selecionado */
input[type=checkbox].estilizado:checked:before {
    content: "✔";
    font-size: 15px;
    color: rgba( 0,0,0,0.5 );
    position: relative;
    top: -6px;
    left: 2px;
}

E continuando com os estilos adicionais dos radio buttons:

input[type=radio].estilizado {
    width: 13px;
    height: 13px;
    border-radius: 7px;
}

/* quando o radio button estiver selecionado */
input[type=radio].estilizado:checked:before {
    content: "•";
    font-size: 15px;
    color: #333;
    position: relative;
    top: -5px;
    left: 1px;
}

O resultado encontra-se abaixo, teste você mesmo (o focus também)!

Futuramente

Eu gostaria de continuar aqui com outros widgets como <select> e os inputs dos tipos file, number, color, datetime e seus variantes, mas não encontrei mais seletores especiais – com exceção do seletor ::-webkit-inner-spin-button e ::-webkit-outer-spin-button para <input type="number">, que infelizmente não nos permitem fazer nada de muito legal.

No caso do <select>, sabemos que este já é parcialmente customizável, pois podemos alterar a aparência do campo de texto, mas não podemos ainda modificar a sua lista de opções e nem o seu botão – a não ser, colocando uma imagem de fundo para o elemento inteiro, como nestes exemplos (em inglês), mas não acho isso nada legal =P

Acredito que o suporte a estes tipos de customização está melhorando muito rápido (em parte graças ao Webkit), e aos poucos estamos caminhando para finalmente ter o controle total de todas essas ferramentas que tanto usamos.

#16