Contenu principal

Personnalisation des checkbox et boutons radio sans image

Dans la lignée de mon précédent article Personnaliser les checkbox en CSS façon iOS, nous allons utiliser le même principe pour personnaliser des checkbox et des boutons radio sans image, en n’utilisant que du CSS (CSS3 principalement), mais en gardant un aspect boite à cocher et bouton radio cette fois.

Nous partons avec les mêmes handicaps imposés, à savoir : aucune image, et un balisage html réduit à son minimum : l’input et son label.

Marquage HTML

Aujourd’hui je vous propose 3 exemples pour les boites à cocher, et 1 pour les boutons radio. Voici donc la structure de départ :

0102030405060708091011121314

<ul class="choices-border">
	<li><input type="checkbox" name="check-1" id="check-1" /><label for="check-1">Choix 1</label></li>
	<li><input type="checkbox" name="check-2" id="check-2" /><label for="check-2">Choix 2</label></li>
	<li><input type="radio" name="radio-1" id="radio-1" /><label for="radio-1">Radio 1</label>
		<input type="radio" name="radio-1" id="radio-2" checked="checked" /><label for="radio-2">Radio 2</label>
		<input type="radio" name="radio-1" id="radio-3" /><label for="radio-3">Radio 3</label>
	</li>
</ul>
<ul class="choices-text">
	<li><input type="checkbox" name="check-3" id="check-3" /><label for="check-3">Choix 3</label></li>
</ul>
<ul class="choices-thin">
	<li><input type="checkbox" name="check-4" id="check-4" /><label for="check-4">Choix 4</label></li>
</ul>

Base CSS commune

Dans la même veine que mon précédent article, nous allons masquer les inputs, et utiliser les pseudo éléments :before et :after du label pour créer respectivement le fond de nos « fausses inputs » et leur… Comment appeler ça au fait? Ha oui, je l’ai : leur machin-qui-indique-qu’elles-sont-activées… >_> Bon ok c’est trop long, on va dire « coche » alors.
On aura donc toute une base CSS commune, les différences entre mes exemples seront au niveau de :after.

[Aparté]
Au passage, j’en profite pour introduire l’unité rem. Pour ceux qui ne connaissent pas, c’est un peu un croisement entre le px et le em, et profitant du meilleur des 2 mondes. En fait, le rem est très proche de em, avec comme différence le « r » (non, ne me jetez pas les cailloux déjà, attendez) qui signifie « root » (racine) : em donne une taille relative au parent le plus proche (par exemple si le parent a une taille de caractères de 10px et que son élément fils a une taille de 1.5em, il en résulterait 15px), alors que rem donne une taille relative à la racine du document (balise html donc), ce qui pose beaucoup moins de problèmes au niveau des imbrications de balises.
Il y a bien sûr les adeptes du pixel, et ceux du em, on ne rentrera pas dans ce débat aujourd’hui bien sûr, ce n’est pas le but.
L’avantage du em (et donc du rem) est de tenir compte de la taille de caractère réglée dans le navigateur du visiteur. Par défaut, cette taille de caractère est de 16px. Donc, avec les réglages par défaut, et pour simplifier, 1em = 1rem = 16px. Du coup, 1rem = 0.625px, ça commence à se compliquer :/ Mais, il y a bien sûr une astuce pour simplifier tout ça, pour en savoir plus je vous conseille le très bon article Font sizing with rem [en], où Jonathan Snook expose sa méthode pour, à la fois simplifier grandement les calculs tout en évitant d’avoir 4 chiffres derrière la virgule, et aussi pour tenir compte des navigateurs n’ayant pas encore implémenté cette unité.
Pour résumer sa méthode, on fixe le font-size à 62.5% sur la balise <html> (16px x 62.5% = 10px). Maintenant nous avons donc notre « root » à 10px, donc 1rem = 10px, donc 1px = 0.1rem. C’est quand même beaucoup plus pratique à utiliser :)
Pour la compatibilité navigateurs, je suppose que IE9 l’a implémenté, il reste Opera qui ne l’a pas encore fait pour l’heure. Il faut donc prévoir un fallback en pixels :

1234

a.foo {
	font-size: 16px;
	font-size: 1.6rem;
}

Bien sûr, cette unité ne se limite pas à la taille de caractère, mais peut être utilisée pour dimensionner des éléments.
[/Aparté]

Masquer les inputs

Même chose que dans le tutoriel précédent : on masque les inputs seulement sur les navigateurs récents, tout en conservant la navigation au clavier.

0102030405060708091011121314151617

input[type="checkbox"]:checked,
input[type="checkbox"]:not(:checked),
input[type="radio"]:checked,
input[type="radio"]:not(:checked) {
	width: 1px;
	height: 1px;
	margin: 0;
	padding: 0;
	opacity: 0;
	position: absolute;
	clip: rect(0 0 0 0);
	clip: rect(0,0,0,0);
	-webkit-appearance:	none;
	-moz-appearance:	none;
	-o-appearance:		none;
	appearance:		none;
}

Le label

Le seul point à noter est padding-left, c’est dans cet espace que viendront se loger nos fake inputs.

1819202122232425262728

#foo:checked:before,
input[type="checkbox"]+label,
input[type="radio"]+label {
	float: left;
	padding-left: 26px;
	padding-left: 2.6rem;
	margin-right: 16px;
	margin-right: 1.6rem;
	position: relative;
	cursor: pointer;
}

Le fond de nos fausses inputs

Nos inputs sont positionnées en absolu à gauche de leur label, des coins arrondis avec border-radius, un fond dégradé blanc de 40% d’opacité en haut vers 90% en bas, et des ombres à l’intérieur (inset) et à l’extérieur avec box-shadow pour donner de volume. La seule différence entre les checkbox et les boutons radio est au niveau de border-radius.

29303132333435363738394041424344454647484950515253545556575859606162636465666768

#foo:checked,
input[type="checkbox"]+label:before,
input[type="radio"]+label:before {
	content: '';
	position: absolute;
	left: 0;
	top: 50%;
	margin-top: -9px;
	margin-top: -.9rem;
	width: 16px;
	width: 1.6rem;
	height: 16px;
	height: 1.6rem;
	background-color: rgba(255,255,255,.7);
	background:	-webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,.4)), to(rgba(255,255,255,.9)));
	background:	-webkit-linear-gradient(top, rgba(255,255,255,.4), rgba(255,255,255,.9));
	background:	   -moz-linear-gradient(top, rgba(255,255,255,.4), rgba(255,255,255,.9));
	background:	     -o-linear-gradient(top, rgba(255,255,255,.4), rgba(255,255,255,.9));
	background:		linear-gradient(top, rgba(255,255,255,.4), rgba(255,255,255,.9));
	-webkit-box-shadow:	inset .1rem .1rem .2rem rgba(0,0,0,.3), inset 0 0 0 .1rem rgba(0,0,0,.1);
	-moz-box-shadow:	inset .1rem .1rem .2rem rgba(0,0,0,.3), inset 0 0 0 .1rem rgba(0,0,0,.1);
	-o-box-shadow:		inset 1px 1px 2px rgba(0,0,0,.3), inset 0 0 0 1px rgba(0,0,0,.1);
	-o-box-shadow:		inset .1rem .1rem .2rem rgba(0,0,0,.3), inset 0 0 0 .1rem rgba(0,0,0,.1);
	box-shadow:		inset 1px 1px 2px rgba(0,0,0,.2), inset 0 0 0 1px rgba(0,0,0,.1);
	box-shadow:		inset .1rem .1rem .2rem rgba(0,0,0,.3), inset 0 0 0 .1rem rgba(0,0,0,.1);
	-webkit-border-radius:	.3rem;
	-moz-border-radius:	.3rem;
	-o-border-radius:	3px;
	-o-border-radius:	.3rem;
	border-radius:		3px;
	border-radius:		.3rem;
}
input[type="radio"]+label:before {
	-webkit-border-radius:	.8rem;
	-moz-border-radius:	.8rem;
	-o-border-radius:	8px;
	-o-border-radius:	.8rem;
	border-radius:		8px;
	border-radius:		.8rem;
}

Montrer le focus

On va juste rajouter une bordure grise autour de nos fausses inputs pour montrer le focus, en tenant compte de la valeur précédente de box-shadow bien sûr.

697071727374757677

input[type='checkbox']:focus + label:before,
input[type='radio']:focus + label:before {
	-webkit-box-shadow:	inset .1rem .1rem .2rem rgba(0,0,0,.3), inset 0 0 0 .1rem rgba(0,0,0,.1), 0 0 0 .3rem rgba(0,0,0,.4);
	-moz-box-shadow:	inset .1rem .1rem .2rem rgba(0,0,0,.3), inset 0 0 0 .1rem rgba(0,0,0,.1), 0 0 0 .3rem rgba(0,0,0,.4);
	-o-box-shadow:		inset 1px 1px 2px rgba(0,0,0,.3), inset 0 0 0 1px rgba(0,0,0,.1), 0 0 0 3px rgba(0,0,0,.4);
	-o-box-shadow:		inset .1rem .1rem .2rem rgba(0,0,0,.3), inset 0 0 0 .1rem rgba(0,0,0,.1), 0 0 0 .3rem rgba(0,0,0,.4);
	box-shadow:		inset 1px 1px 2px rgba(0,0,0,.2), inset 0 0 0 1px rgba(0,0,0,.1), 0 0 0 3px rgba(0,0,0,.4);
	box-shadow:		inset .1rem .1rem .2rem rgba(0,0,0,.3), inset 0 0 0 .1rem rgba(0,0,0,.1), 0 0 0 .3rem rgba(0,0,0,.4);
}

Transitions CSS

Nous mettons en place une transition sur la coche (pseudo-élément :after), qui va agir sur la propriété transform, elle servira lors du clic sur le label (le machin-qui-indique-qu’elles-sont-activées va apparaitre en grossissant et disparaitre en rapetissant) et lors du survol avec la souris.

787980818283

input+label:after {
	-webkit-transition:	-webkit-transform 0.2s ease-in-out;
	-moz-transition:	   -moz-transform 0.2s ease-in-out;
	-o-transition:		     -o-transform 0.2s ease-in-out;
	transition:			transform 0.2s ease-in-out;
}

Checkbox version « traits verts »

Notre coche va être positionnée en absolu, comme le fond. Pour la forme, il s’agit d’un bloc transparent comportant une bordure verte en bas et à gauche. A noter la différence d’épaisseur entre les 2 bordures, c’est pour compenser la déformation ultérieure.

084085086087088089090091092093094095096097098099100101102103104105106107

#foo:checked,
.choices-border input[type="checkbox"]+label:after {
	content: '';
	position: absolute;
	left: 7px;
	left: .7rem;
	top: 50%;
	margin-top: -4px;
	margin-top: -.4rem;
	width: 14px;
	width: 1.4rem;
	height: 6px;
	height: .6rem;
	border-style: solid;
	border-color: #01C30C;
	border-width: 0 0 2px 3px;
	border-width: 0 0 .2rem .3rem;
	-webkit-box-shadow:	-.1rem .1rem .1rem 0 rgba(0,0,0,.4);
	-moz-box-shadow:	-.1rem .1rem .1rem 0 rgba(0,0,0,.4);
	-o-box-shadow:		-1px 1px 1px 0 rgba(0,0,0,.4);
	-o-box-shadow:		-.1rem .1rem .1rem 0 rgba(0,0,0,.4);
	box-shadow:		-1px 1px 1px 0 rgba(0,0,0,.4);
	box-shadow:		-.1rem .1rem .1rem 0 rgba(0,0,0,.4);
}

A ceci nous ajoutons transform. Nous avons notre sorte de « L » (sauf qu’il est plus large que haut), il faut d’abord fixer le point d’origine des transform, c’est très important pour les transitions. Nous fixons donc transform-origin à « 0 100% », c’est à dire à l’intersection des 2 bords. Ensuite, on utilise skewX(-30deg) pour déformer la coche (d’où la bordure plus épaisse à gauche), ça aura pour effet de tirer le haut de notre coche vers la droite (on obtient une sorte de « V » couché sur la droite), puis une rotation dans le sens inverse des aiguilles d’une montre avec rotate(-40deg). Pour finir, notre coche ne doit pas être visible à l’état « normal », on utilise donc scale(0).

108109110111112113114115116117118

#foo:checked,
.choices-border input[type="checkbox"]+label:after {
	-webkit-transform:	rotateZ(-40deg) skewX(-30deg) scale(0);
	-moz-transform:		rotate(-40deg) skewX(-30deg) scale(0);
	-o-transform:		rotate(-40deg) skewX(-30deg) scale(0);
	transform:		rotate(-40deg) skewX(-30deg) scale(0);
	-webkit-transform-origin:	0 100%;
	-moz-transform-origin:		0 100%;
	-o-transform-origin:		0 100%;
	transform-origin:		0 100%;
}

État coché

Nous passons juste scale() à 1.

119120121122123124

.choices-border input[type="checkbox"]:checked+label:after {
	-webkit-transform:	rotateZ(-40deg) skewX(-30deg) scale(1);
	-moz-transform:		rotate(-40deg) skewX(-30deg) scale(1);
	-o-transform:		rotate(-40deg) skewX(-30deg) scale(1);
	transform:		rotate(-40deg) skewX(-30deg) scale(1);
}

État hover

On augmente la taille de la coche de 30% au survol de la souris.

125126127128129130

.choices-border input[type="checkbox"]:checked+label:hover:after {
	-webkit-transform:	rotateZ(-40deg) skewX(-30deg) scale(1.3);
	-moz-transform:		rotate(-40deg) skewX(-30deg) scale(1.3);
	-o-transform:		rotate(-40deg) skewX(-30deg) scale(1.3);
	transform:		rotate(-40deg) skewX(-30deg) scale(1.3);
}

Et voilà, nous avons terminé la première version « traits verts ».

Checkbox version « thin »

Une version quasi-similaire à la précédente, donc je vais vous donner le code d’un seul coup. Les différences : les 2 bordures seront en noir avec opacité à 40%, la coche sera plus petite et ne dépassera pas de son fond, pas de déformation avec scale(), juste une rotation à 45 degrés.

131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168

#foo:checked,
.choices-thin input[type="checkbox"]+label:after {
	content: '';
	position: absolute;
	left: 7px;
	left: .7rem;
	top: 50%;
	margin-top: -2px;
	margin-top: -.2rem;
	width: 8px;
	width: .8rem;
	height: 4px;
	height: .4rem;
	border-style: none none solid solid;
	border-color: transparent transparent rgba(0,0,0,.4) rgba(0,0,0,.4);
	border-width: 0 0 2px 2px;
	border-width: 0 0 .2rem .2rem;
	-webkit-transform:	rotate(-45deg) scale(0);
	-moz-transform:		rotate(-45deg) scale(0);
	-o-transform:		rotate(-45deg) scale(0);
	transform:		rotate(-45deg) scale(0);
	-webkit-transform-origin:	0 100%;
	-moz-transform-origin:		0 100%;
	-o-transform-origin:		0 100%;
	transform-origin:		0 100%;
}
.choices-thin input[type="checkbox"]:checked+label:after {
	-webkit-transform:	rotate(-45deg) scale(1);
	-moz-transform:		rotate(-45deg) scale(1);
	-o-transform:		rotate(-45deg) scale(1);
	transform:		rotate(-45deg) scale(1);
}
.choices-thin input[type="checkbox"]:checked+label:hover:after {
	-webkit-transform:	rotate(-45deg) scale(1.2);
	-moz-transform:		rotate(-45deg) scale(1.2);
	-o-transform:		rotate(-45deg) scale(1.2);
	transform:		rotate(-45deg) scale(1.2);
}

Checkbox version « texte »

Cette fois nous n’allons pas utiliser la propriété border pour faire notre coche, mais simplement le caractère « v » avec l’attribut content. Histoire de donner un style manuscrit à notre « v », on lui applique la fonte « Comic Sans MS » (HOOOUUUUUU honte à moi!). A part ça, la méthode reste presque la même que pour les autres versions, on remplace rotate() par un skewY(), et on modifie transform-origin().

169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204

#foo:checked,
.choices-text input[type="checkbox"]+label:after {
	content: 'v';
	position: absolute;
	left: 2px;
	left: .2rem;
	top: 50%;
	margin-top: -17px;
	margin-top: -1.7rem;
	width: 14px;
	width: 1.4rem;
	font-family: "Comic Sans MS", cursive;
	color: #333;
	font-size: 24px;
	font-size: 2.4rem;
	-webkit-transform:	skewX(-25deg) skewY(-40deg) scale(0);
	-moz-transform:		skewX(-25deg) skewY(-40deg) scale(0);
	-o-transform:		skewX(-25deg) skewY(-40deg) scale(0);
	transform:		skewX(-25deg) skewY(-40deg) scale(0);
	-webkit-transform-origin:	50% 100%;
	-moz-transform-origin:		50% 100%;
	-o-transform-origin:		50% 100%;
	transform-origin:		50% 100%;
}
.choices-text input[type="checkbox"]:checked+label:after {
	-webkit-transform:	skewX(-25deg) skewY(-40deg) scale(1);
	-moz-transform:		skewX(-25deg) skewY(-40deg) scale(1);
	-o-transform:		skewX(-25deg) skewY(-40deg) scale(1);
	transform:		skewX(-25deg) skewY(-40deg) scale(1);
}
.choices-text input[type="checkbox"]:checked+label:hover:after {
	-webkit-transform:	skewX(-25deg) skewY(-40deg) scale(1.3);
	-moz-transform:		skewX(-25deg) skewY(-40deg) scale(1.3);
	-o-transform:		skewX(-25deg) skewY(-40deg) scale(1.3);
	transform:		skewX(-25deg) skewY(-40deg) scale(1.3);
}

Boutons radio

Cette fois c’est moins compliqué pour faire la coche puisqu’il s’agit d’un disque. Hauteur et largeur sont donc identiques, border-radius est fixé à 50% (un fallback en rem est prévu pour Safari qui n’aime pas trop les pourcentages, je ne sais pour quelle raison). Ensuite, tout est une histoire de couleur de fond et de box-shadow. La seule transformation que nous gardons est scale() pour conserver la transition. Cette fois, inutile de préciser un transform-origin puisque nous souhaitons nous positionner au centre du disque, soit « 50% 50% », c’est à dire la valeur par défaut.

205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246

#foo:checked,
input[type="radio"]+label:after {
	content: '';
	position: absolute;
	left: 2px;
	left: .2rem;
	top: 50%;
	margin-top: -7px;
	margin-top: -.7rem;
	width: 12px;
	width: 1.2rem;
	height: 12px;
	height: 1.2rem;
	background-color: #FD6800;
	-webkit-border-radius:	.6rem;
	-webkit-border-radius:	50%;
	-moz-border-radius:	50%;
	-o-border-radius:	50%;
	border-radius:		50%;
	-webkit-box-shadow:	inset 0 .1rem .2rem rgba(255,255,255,.6), inset 0 -.1rem .1rem 0 rgba(0,0,0,.5), 0 0 .3rem #000;
	-moz-box-shadow:	inset 0 .1rem .2rem rgba(255,255,255,.6), inset 0 -.1rem .1rem 0 rgba(0,0,0,.5), 0 0 .3rem #000;
	-o-box-shadow:		inset 0 1px 2px rgba(255,255,255,.6), inset 0 -1px 1px 0 rgba(0,0,0,.5), 0 0 3px #000;
	-o-box-shadow:		inset 0 .1rem .2rem rgba(255,255,255,.6), inset 0 -.1rem .1rem 0 rgba(0,0,0,.5), 0 0 .3rem #000;
	box-shadow:		inset 0 1px 2px rgba(255,255,255,.6), inset 0 -1px 1px 0 rgba(0,0,0,.5), 0 0 3px #000;
	box-shadow:		inset 0 .1rem .2rem rgba(255,255,255,.6), inset 0 -.1rem .1rem 0 rgba(0,0,0,.5), 0 0 .3rem #000;
	-webkit-transform:	scale(0);
	-moz-transform:		scale(0);
	-o-transform:		scale(0);
	transform:		scale(0);
}
input[type="radio"]:checked+label:after {
	-webkit-transform:	scale(1);
	-moz-transform:		scale(1);
	-o-transform:		scale(1);
	transform:		scale(1);
}
input[type="radio"]:checked+label:hover:after {
	-webkit-transform:	scale(1.3);
	-moz-transform:		scale(1.3);
	-o-transform:		scale(1.3);
	transform:		scale(1.3);
}

Le travail est fini! :)

See ya!