Ah ! Les fameux CTA !
Un site sans « bouton » ou du moins des éléments qui y ressemblent, il n'est pas dit que ça existe.
En soi, ce n'est pas une chose compliquée à faire. Là où ça peut commencer à se corser, c'est lorsqu'il s'agit d'avoir moult couleurs, puis des pictogrammes plus ou moins à droite ou à gauche, le tout dans différentes dimensions. Et comme on apprécie la difficulté en général, il faut penser à l'accessibilité (encore), à la maintenance, à l'évolution et dans certains cas à l'industrialisation de ces petites choses.
Dans les lignes qui suivent, vous trouverez une base de travail assez simple. Tout est basé sur l'utilisation des variables Cascading Style Sheets.
Les principes de base :
- Les CTA utilisent comme référence de hauteur la même variable que la plus grande gouttière du viewport courant (voir le fichier de variables plus bas) ;
- Les couleurs appliquées sont pour la quasi-totalité celles de la palette du site. Ces couleurs sont aussi définies dans le fichier de variables CSS. À vous de choisir les vôtres !
- Les pictogrammes sont proportionnels aux dimensions des CTA ;
- Si vous gérez un mode sombre (comme sur ce site), tout va hériter, et c'est là que ça devient vraiment marrant !
Ensuite, plutôt que de définir maintes versions de ces CTA, nous allons juste fixer une version « de base » avec une classe .cta et ensuite changer les variables de couleurs pour chaque sous-catégorie.
Au final, pour ajouter une variante, il n'y a plus que 2 déclarations de CSS à faire.
- La version d'affichage primaire ;
- La version au
:hover, mais seulement applicable si le terminal a un curseur « fin » (@media (any-pointer: fine). Eh oui ! Si on ne filtre pas ce medium, on se retrouve souvent à devoir taper deux fois sur un écran tactile.- Pour simuler le
:hover; - Pour effectuer réellement le clic.
Dans la vraie vie, c'est pénible. Alors autant y penser.
- Pour simuler le
Voilà ! Et pour presque terminer, ce principe et cette feuille de style s'applique aussi sur des liens. Pourquoi ? Parce qu'un CTA c'est tout aussi bien un lien qu'un bouton et que chacun a ses spécificités.
- Un lien sert à changer de contexte. Le plus souvent il s'agit de changer de page ;
- Un bouton sert à effectuer une action (soumettre un formulaire, ouvrir une popin dans la page, etc.).
Les interactions clavier ne sont pas les mêmes et pour l'accessibilité ça change tout !
Et maintenant, au boulot…
CTA simples
La classe CSS de base à spécifier sur les button est .cta.
<button type="button" class="cta">•••</button>
<a href="[lien]" class="cta">•••</a>
Dès qu'il y a une classe spécifique, .cta-secondary, .cta-link, .cta-dark, etc., il n'y a pas besoin préciser la classe .cta grâce au sélecteur CSS [class*="cta"].
Par exemple, avec les couleur de Kortic, ça donne ça.
Exemples de code
<button class="cta" type="button">
<!-- couleur de base = couleur des liens -->
.cta (défaut)
</button>
<button class="cta-secondary" type="button">
<!-- couleur de base = couleur des liens -->
.cta-secondary
</button>
etc.
Etc. avec toutes les couleurs souhaitées.
CTA avec pictogrammes
Par défaut, les pictogrammes prennent la couleur du texte du bouton. Au :hover il n'y a pas besoin de gérer le changement des couleurs qui héritent du bouton.
Le fonctionnement est simple. La base du lien ou du bouton est la même.
Il suffit d'ajouter la classe .cta-icon, et bien entendu un pictogramme dans l'élément. Le choix a été fait d'intégrer des SVG directement dans les éléments.
Etc. pour toutes les variantes de couleurs.
Exemple de code
<button class="cta-icon cta-right cta-secondary" type="button">
<svg aria-hidden="true" width="32" height="32"><use href="#icon-colibri"></use></svg>
.cta-secondary
</button>
Pictogrammes seuls
Mais accessibles ! Le code diffère un peu car il faut ajouter un span en plus du svg.
Codes des boutons pictogrammes
<button class="cta-icon icon-only" type="button">
<svg aria-hidden="true" width="32" height="32"><use href="#icon-close"></use></svg>
<span>Fermer</span>
</button>
<button class="cta-icon icon-only cta-destructive cta-small" type="button">
<svg aria-hidden="true" width="32" height="32"><use href="#icon-delete"></use></svg>
<span>Supprimer</span>
</button>
Gestion du responsive
En affichage inférieur à 30rem (480px), les CTA prennent la largeur de leur conteneur, sauf les CTA à pictogramme seul.
Pour bloquer ce comportement, il suffit d'ajouter au CTA la classe no-fit ou no-stretch.
Les boutons ne reviennent jamais à la ligne. En cas de débordement du texte, un ellipsis est prévu.
Les styles
Et voilà la feuille de styles complète. Il faudra tout de même créer les variables personnalisées de votre système de design, mais vous avez une base saine maintenant.
Taille brute : 10,24 kilo-octets (~2,18 kilo-octets gzip)
/* Définition des dimensions de CTA selon les viewports */
@media (max-width: 63.9375rem) {
:root {
--button-height: max(2.75rem, calc(var(--gutter, 2.5rem) + var(--tiny-gutter, 0.3125rem)));
.call-to-action-tiny {
--button-height: max(1.75rem, calc(var(--half-gutter, 1.25rem) + var(--tiny-gutter, 0.3125rem)));
}
}
}
@media (min-width: 64rem) {
:root {
--button-height: max(3rem, var(--gutter, 2.5rem));
.call-to-action-tiny {
--button-height: max(1.75rem, var(--half-gutter, 1.25rem));
}
}
}
/* Couleurs */
:root {
color-scheme: dark light;
--button-primary-foreground: var(--basic-color-reverse, rgb(0,0,0));
--button-primary-background: light-dark(
var(--link-color, rgb(61,50,128)),
var(--link-color, rgb(105,94,172))
);
}
/* Base buttons */
button {
font-family: inherit;
position: relative;
font-weight: inherit;
main & {
background-color: transparent;
color: var(--canvasText, currentColor);
font-size: inherit;
&:not([class*='cta']) {
line-height: inherit;
}
}
}
/* Couleurs de base pour l'ensemble des CTA */
[class*="cta"] {
--button-shadow-stroke: 0.0313rem;
/*Gestion du contraste système*/
@media (prefers-contrast: more) {
--button-shadow-stroke: 0.125rem;
}
/*Gestion du contraste widget local*/
@media (prefers-contrast: no-preference) {
:root.contrast & {
--button-shadow-stroke: 0.125rem;
}
}
--button-background: var(--button-primary-background, black);
--button-foreground: var(--button-primary-foreground, white);
--button-box-shadow: var(--button-primary-background, black);
@media (forced-colors: none) {
border: none;
}
border-radius: var(--button-radius, 0.3125rem);
background-color: var(--button-background);
box-shadow: 0 0 0 var(--button-shadow-stroke) var(--button-box-shadow) inset;
color: var(--button-foreground);
box-sizing: border-box;
font-family: inherit;
font-size: 1rem;
font-weight: inherit;
height: var(--button-height);
letter-spacing: initial;
line-height: var(--button-height);
max-width: 100%;
min-width: var(--button-height);
outline-offset: 0.1875rem;
overflow: hidden;
padding: 0 calc(var(--button-height) / 2.5);
position: relative;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
/* Tous les enfants héritent de la couleur */
* {
color: inherit !important;
}
/* Non-button elements */
&:not(button) {
text-decoration: none !important;
display: inline-block;
}
/* Disabled state */
&:is(.disabled, :disabled) {
opacity: .3;
pointer-events: none;
}
/* Firefox fix */
&::-moz-focus-inner {
border: 0;
padding: 0;
}
}
/* Focus state with contrast */
:root.use-keyboard.contrast [class*='cta']:focus {
box-shadow: 0 0 0 var(--button-shadow-stroke) var(--button-box-shadow) inset;
}
/* Hover states */
@media (any-hover: hover) {
[class*='cta'] {
cursor: pointer;
&:hover {
text-decoration: none !important;
--button-background: var(--link-color-alternate, blue);
--button-foreground: var(--basic-color-reverse, white);
--button-box-shadow: transparent;
}
}
}
/* Variantes de couleurs */
.cta-secondary {
--button-background: var(--basic-color-reverse);
--button-foreground: var(--link-color);
--button-box-shadow: var(--link-color);
@media (any-hover: hover) {
&:hover {
--button-background: var(--basic-color-reverse);
--button-foreground: var(--link-color-alternate);
--button-box-shadow: var(--link-color-alternate);
}
}
}
.cta-tertiary {
--button-background: var(--background-color-menus);
--button-foreground: var(--canvasText);
--button-box-shadow: light-dark(var(--box-shadow), transparent);
:root.contrast & {
@media (prefers-color-scheme: dark) {
--button-box-shadow: var(--box-shadow);
}
}
@media (any-hover: hover) {
&:hover {
--button-background: var(--background-color-menus);
--button-foreground: var(--heading-color);
--button-box-shadow: var(--box-shadow);
}
}
}
.cta-link {
--button-background: transparent;
--button-foreground: var(--link-color);
--button-box-shadow: transparent !important;
@media (any-hover: hover) {
&:hover {
--button-background: transparent;
--button-foreground: var(--link-color-alternate);
--button-box-shadow: transparent !important;
}
}
}
.cta-destructive {
--button-background: var(--raspberry, red);
--button-foreground: white;
--button-box-shadow: transparent;
@media (any-hover: hover) {
&:hover {
--button-background: rgb(230,0,35);
--button-foreground: white;
--button-box-shadow: transparent;
}
}
}
.cta-warn {
--button-background: var(--orange, orange);
--button-foreground: rgb(255,255,255);
--button-box-shadow: transparent;
@media (any-hover: hover) {
&:hover {
--button-background: var(--yellow);
--button-foreground: rgb(0,0,0);
--button-box-shadow: transparent;
}
}
}
.cta-success {
--button-background: var(--applegreen, green);
--button-foreground: var(--basic-color-reverse);
--button-box-shadow: transparent;
@media (any-hover: hover) {
&:hover {
--button-background: var(--green);
--button-foreground: var(--basic-color-reverse);
--button-box-shadow: transparent;
}
}
}
.cta-dark {
--button-background: black;
--button-foreground: rgb(255,255,255);
--button-box-shadow: transparent;
@media (any-hover: hover) {
&:hover {
--button-background: rgb(34,34,34);
--button-foreground: white;
--button-box-shadow: transparent;
}
@media (prefers-color-scheme: dark) {
:root.contrast & {
--button-box-shadow: darkgray;
&:hover {
--button-box-shadow: currentColor;
}
}
}
}
}
.cta-light {
--button-background: white;
--button-foreground: black;
--button-box-shadow: lightgray;
@media (any-hover: hover) {
&:hover {
--button-background: lightgray;
--button-foreground: black;
--button-box-shadow: lightgray;
}
}
}
.cta-reverse {
--button-background: var(--basic-color, black);
--button-foreground: var(--basic-color-reverse, white);
--button-box-shadow: var(--basic-color, black);
@media (any-hover: hover) {
&:hover {
--button-background: var(--basic-color-reverse, white);
--button-foreground: var(--basic-color, black);
--button-box-shadow: var(--basic-color, black);
}
}
}
.cta-text {
--button-background: transparent;
--button-foreground: var(--canvasText, black);
--button-box-shadow: transparent;
@media (any-hover: hover) {
&:hover {
--button-background: transparent;
--button-foreground: var(--basic-color, black);
--button-box-shadow: transparent;
}
}
}
.cta-canvas {
--button-background: transparent;
--button-foreground: CanvasText;
--button-box-shadow: transparent;
@media (any-hover: hover) {
&:hover {
text-decoration: underline !important;
text-underline-position: under;
--button-background: transparent;
--button-foreground: CanvasText;
--button-box-shadow: transparent;
}
}
}
/* Tailles */
.cta-small {
height: calc(var(--button-height) * .6);
line-height: calc(var(--button-height) * .6) !important;
min-width: calc(var(--button-height) * .6);
padding: 0 calc(var(--button-height) / 5);
border-radius: calc(var(--button-radius, 0.3125rem) / 2);
&:not(.font-size-larger) {
font-size: 0.8125rem;
}
}
:root .call-to-action-tiny [class*='cta']:not(.font-size-larger) {
font-size: 0.8125em;
border-radius: calc(var(--button-radius, 0.3125rem) / 2);
}
/* Gestion des CTA avec icônes */
.cta-icon {
&:not(.cta-right) {
padding-left: var(--button-height);
> svg {
left: calc(var(--button-height) / 1.8);
transform: translate(-50%, -50%);
}
}
&.cta-right {
padding-right: var(--button-height);
> svg {
right: calc(var(--button-height) / 1.8);
transform: translate(50%, -50%);
}
}
&.cta-small {
&:not(.cta-right) {
padding-left: calc(var(--button-height) / 1.5);
> svg {
left: calc(var(--button-height) / 2.8);
}
}
&.cta-right {
padding-right: calc(var(--button-height) / 1.5);
> svg {
right: calc(var(--button-height) / 2.5);
}
}
> svg {
width: calc(var(--button-height) / 2.6);
height: calc(var(--button-height) / 2.6);
}
}
> svg {
width: calc(var(--button-height) / 1.9);
height: calc(var(--button-height) / 1.9);
position: absolute;
top: 50%;
}
use {
fill: currentColor;
}
/* Icon only */
&.icon-only {
padding: 0 !important;
> svg {
left: 50% !important;
right: 50% !important;
transform: translate(-50%, -50%);
width: 60%;
height: 60%;
}
span {
position: absolute;
width: 0;
height: 0;
line-height: 0;
z-index: -10;
border: 0;
text-indent: -0.625rem;
white-space: nowrap;
overflow: hidden;
}
&:is(.cta-small, .call-to-action-tiny &) {
box-shadow: 0 0 0 0.0625rem var(--button-box-shadow) inset;
}
/* No border variant */
&.cta-no-border {
:root:not(.contrast) & {
--button-box-shadow: transparent;
border-radius: var(--button-height);
}
}
}
}
/* Mobile responsive */
@media (max-width: 30rem) {
[class*='cta']:not(:is(.no-fit, .no-stretch, .icon-only)) {
width: 100%;
margin-bottom: 0.625rem;
}
.cta-icon {
&:not(:is(.no-fit, .no-stretch, .cta-right)) {
padding-right: var(--button-height);
}
&.cta-right:not(:is(.no-fit, .no-stretch)) {
padding-left: var(--button-height);
}
&.cta-small {
&:not(:is(.no-fit, .no-stretch, .cta-right)) {
padding-right: calc(var(--button-height) / 1.5);
}
&.cta-right:not(:is(.no-fit, .no-stretch)) {
padding-left: calc(var(--button-height) / 1.5);
}
}
}
}
/* Bouton arrondi */
.cta-round {
border-radius: var(--button-height);
}
That's all, Folks !