Créer un composant de bouton

Présentation de base sur la création de composants <button> adaptatifs, responsifs et accessibles aux couleurs.

Dans cet article, je souhaite partager mes réflexions sur la création d'un élément <button> adaptatif, adaptatif et accessible aux couleurs. Essayez la démonstration et consultez la source.

Les boutons fonctionnent avec le clavier et la souris dans les thèmes clair et sombre.

Si vous préférez la vidéo, voici une version YouTube de ce post:

Présentation

Navigateurs pris en charge

  • 1
  • 12
  • 1
  • ≤4

Source

L'élément <button> est conçu pour les interactions utilisateur. Son événement click se déclenche au clavier, à la souris, au toucher, à la voix, etc., avec des règles intelligentes sur la durée. Il est également fourni avec des styles par défaut dans chaque navigateur, afin que vous puissiez les utiliser directement sans aucune personnalisation. Utilisez color-scheme pour activer également les boutons clair et sombre fournis par le navigateur.

Il existe également différents types de boutons, chacun illustré dans le codepen précédent. Une <button> sans type s'adapte pour se trouver dans un <form>, en changeant au type d'envoi.

<!-- buttons -->
<button></button>
<button type="submit"></button>
<button type="button"></button>
<button type="reset"></button>

<!-- button state -->
<button disabled></button>

<!-- input buttons -->
<input type="button" />
<input type="file">

Dans le défi de l'interface graphique de ce mois-ci, chaque bouton sera associé à des styles pour aider à différencier visuellement son intention. Les boutons de réinitialisation auront des couleurs d'avertissement car ils sont destructeurs, et les boutons d'envoi auront un texte d'accentuation bleu, de sorte qu'ils apparaissent un peu plus mis en avant que les boutons standards.

Aperçu de l&#39;ensemble final de tous les types de boutons, affiché dans un formulaire et non dans un formulaire, avec des ajouts intéressants pour les boutons en forme d&#39;icône et les boutons personnalisés.
Aperçu de l'ensemble final de tous les types de boutons, affichés dans un formulaire et non dans un formulaire, avec des ajouts intéressants pour les boutons en forme d'icône et les boutons personnalisés

Les boutons possèdent également des pseudo-classes que le CSS peut utiliser pour définir les styles. Ces classes fournissent des hooks CSS pour personnaliser l'aspect du bouton: :hover pour les cas où une souris passe au-dessus du bouton, :active lorsque l'utilisateur appuie sur une souris ou un clavier, et :focus ou :focus-visible pour faciliter l'application d'un style de technologie d'assistance.

button:hover {}
button:active {}
button:focus {}
button:focus-visible {}
Aperçu de l&#39;ensemble final de tous les types de boutons dans le thème sombre.
Aperçu de l'ensemble final de tous les types de boutons pour le thème sombre

Markup

En plus des types de boutons fournis par la spécification HTML, j'ai ajouté un bouton avec une icône et un bouton avec une classe personnalisée btn-custom.

<button>Default</button>
<input type="button" value="<input>"/>
<button>
  <svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
    <path d="..." />
  </svg>
  Icon
</button>
<button type="submit">Submit</button>
<button type="button">Type Button</button>
<button type="reset">Reset</button>
<button disabled>Disabled</button>
<button class="btn-custom">Custom</button>
<input type="file">

Ensuite, à des fins de test, chaque bouton est placé à l'intérieur d'un formulaire. De cette façon, je peux m'assurer que les styles sont correctement mis à jour pour le bouton par défaut, qui se comporte comme un bouton d'envoi. Je change également la stratégie d'icônes, du SVG intégré au SVG masqué, pour m'assurer que les deux fonctionnent aussi bien.

<form>
  <button>Default</button>
  <input type="button" value="<input>"/>
  <button>Icon <span data-icon="cloud"></span></button>
  <button type="submit">Submit</button>
  <button type="button">Type Button</button>
  <button type="reset">Reset</button>
  <button disabled>Disabled</button>
  <button class="btn-custom btn-large" type="button">Large Custom</button>
  <input type="file">
</form>

La matrice de combinaisons est assez écrasante à ce stade. Il existe plus de 20 combinaisons de boutons entre les types de boutons, les pseudo-classes et l'intégration ou la déconnexion d'un formulaire. C'est une bonne chose que le CSS peut nous aider à formuler chacun d'eux clairement.

Accessibilité

Les éléments de bouton sont accessibles naturellement, mais il existe quelques améliorations courantes.

Pointer et effectuer la mise au point ensemble

J'aime regrouper :hover et :focus avec le pseudo-sélecteur fonctionnel :is(). Cela permet de s'assurer que mes interfaces tiennent toujours compte des styles de clavier et de technologie d'assistance.

button:is(:hover, :focus) {
  …
}
Essayez une démonstration !

Anneau de mise au point interactif

J'aime animer l'anneau de mise au point pour les utilisateurs de clavier et de technologies d'assistance. Pour ce faire, j'anime le contour de 5 pixels, mais seulement lorsque le bouton n'est pas actif. Cela crée un effet qui fait que l'anneau de sélection se réduit à la taille du bouton lorsqu'il est enfoncé.

:where(button, input):where(:not(:active)):focus-visible {
  outline-offset: 5px;
}

Garantir un contraste des couleurs correct

Au moins quatre combinaisons de couleurs différentes pour clair et sombre doivent tenir compte du contraste des couleurs: bouton, bouton d'envoi, bouton de réinitialisation et bouton désactivé. VisBug permet d'inspecter et d'afficher tous leurs scores en même temps:

Masquage des icônes pour les personnes qui ne peuvent pas voir

Lors de la création d'un bouton d'icône, l'icône doit fournir un support visuel au texte du bouton. Cela signifie également que l'icône n'est pas utile aux personnes souffrant d'une perte de vue. Heureusement, le navigateur permet de masquer des éléments pour la technologie de lecture d'écran afin que les personnes malvoyantes ne soient pas dérangées par des images de boutons décoratifs:

<button>
  <svg … aria-hidden="true">...</svg>
  Icon Button
</button>
Outils pour les développeurs Chrome montrant l&#39;arborescence d&#39;accessibilité pour le bouton L&#39;arborescence ignore l&#39;image du bouton, car elle est définie sur &quot;true&quot; pour le paramètre aria-hidden.
Outils pour les développeurs Chrome affichant l'arborescence d'accessibilité pour le bouton L'arborescence ignore l'image du bouton, car elle est associée à l'option "aria-hidden" sur "true".

Styles

Dans la section suivante, nous commençons par établir un système de propriétés personnalisé pour gérer les styles adaptatifs du bouton. Je peux commencer à sélectionner des éléments et à personnaliser leur apparence.

Stratégie de propriétés personnalisées adaptatives

La stratégie de propriété personnalisée utilisée dans ce défi de l'IUG est très semblable à celle utilisée pour créer un jeu de couleurs. Pour un système de couleurs clair et sombre adaptatif, une propriété personnalisée est définie et nommée en conséquence pour chaque thème. Une seule propriété personnalisée est ensuite utilisée pour contenir la valeur actuelle du thème et est attribuée à une propriété CSS. Par la suite, la propriété personnalisée unique pourra être mise à jour avec une valeur différente, puis le style du bouton sera mis à jour.

button {
  --_bg-light: white;
  --_bg-dark: black;
  --_bg: var(--_bg-light);

  background-color: var(--_bg);
}

@media (prefers-color-scheme: dark) {
  button {
    --_bg: var(--_bg-dark);
  }
}

Ce que j'aime, c'est que les thèmes clair et sombre sont déclaratifs et clairs. L'indirection et l'abstraction sont déchargées dans la propriété personnalisée --_bg, qui est désormais la seule propriété "réactive". --_bg-light et --_bg-dark sont statiques. Il est également clair que le thème clair est le thème par défaut et que l'option sombre n'est appliquée que de manière conditionnelle.

Préparer la cohérence de la conception

Sélecteur partagé

Le sélecteur suivant permet de cibler tous les différents types de boutons. Au premier abord, il est un peu trop compliqué. :where() est utilisé. La personnalisation du bouton ne nécessite donc aucune spécificité. Les boutons sont souvent adaptés à d'autres scénarios, et le sélecteur :where() garantit la simplicité de la tâche. Dans :where(), chaque type de bouton est sélectionné, y compris ::file-selector-button, qui ne peut pas être utilisé dans :is() ou :where().

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  …
}

Toutes les propriétés personnalisées seront limitées dans ce sélecteur. Passons en revue toutes les propriétés personnalisées. De nombreuses propriétés personnalisées sont utilisées pour ce bouton. Je vais décrire chaque groupe au fur et à mesure, puis partager les contextes de mouvement sombre et réduit à la fin de la section.

Couleur d'accentuation du bouton

Les boutons et les icônes Envoyer sont parfaits pour ajouter une touche de couleur:

--_accent-light: hsl(210 100% 40%);
--_accent-dark: hsl(210 50% 70%);
--_accent: var(--_accent-light);

Couleur du texte du bouton

Les couleurs du texte des boutons ne sont pas le blanc ni le noir. Ce sont des versions sombres ou éclaircies de --_accent qui utilisent hsl() et qui s'appliquent à la teinte 210:

--_text-light: hsl(210 10% 30%);
--_text-dark: hsl(210 5% 95%);
--_text: var(--_text-light);

Couleur de l'arrière-plan du bouton

Les arrière-plans de bouton suivent le même schéma hsl(), à l'exception des boutons du thème clair. Ils sont définis en blanc afin que leur surface les fasse apparaître à proximité de l'utilisateur ou devant d'autres surfaces:

--_bg-light: hsl(0 0% 100%);
--_bg-dark: hsl(210 9% 31%);
--_bg: var(--_bg-light);

Arrière-plan du bouton réussi

Cette couleur d'arrière-plan permet de faire apparaître une surface derrière d'autres surfaces, ce qui est utile pour l'arrière-plan de l'entrée du fichier:

--_input-well-light: hsl(210 16% 87%);
--_input-well-dark: hsl(204 10% 10%);
--_input-well: var(--_input-well-light);

Rembourrage des boutons

L'espacement autour du texte du bouton est effectué à l'aide de l'unité ch, une longueur relative par rapport à la taille de la police. Cela devient essentiel lorsque les gros boutons peuvent simplement augmenter la font-size et que le bouton s'ajuste proportionnellement:

--_padding-inline: 1.75ch;
--_padding-block: .75ch;

Bordure du bouton

L'arrondi de la bordure du bouton est placé dans une propriété personnalisée afin que l'entrée du fichier puisse correspondre aux autres boutons. Les couleurs de bordure suivent le système de couleurs adaptatif établi:

--_border-radius: .5ch;

--_border-light: hsl(210 14% 89%);
--_border-dark: var(--_bg-dark);
--_border: var(--_border-light);

Effet de mise en surbrillance du bouton lors du survol

Ces propriétés définissent une propriété de taille pour la transition en cas d'interaction. La couleur de surlignage suit le système de couleurs adaptatif. Nous verrons comment ces éléments interagissent plus loin dans cet article, mais ils sont au final utilisés pour un effet box-shadow:

--_highlight-size: 0;

--_highlight-light: hsl(210 10% 71% / 25%);
--_highlight-dark: hsl(210 10% 5% / 25%);
--_highlight: var(--_highlight-light);

Ombre du texte du bouton

Chaque bouton présente un style d'ombre de texte subtil. Cela permet au texte de se poser sur le bouton, ce qui améliore la lisibilité et ajoute une belle couche de présentation.

--_ink-shadow-light: 0 1px 0 var(--_border-light);
--_ink-shadow-dark: 0 1px 0 hsl(210 11% 15%);
--_ink-shadow: var(--_ink-shadow-light);

Icône de bouton

Les icônes ont la taille de deux caractères grâce à l'unité de longueur relative ch encore une fois, ce qui aidera l'icône à s'ajuster proportionnellement au texte du bouton. La couleur de l'icône s'appuie sur la --_accent-color pour une couleur adaptative et intégrée au thème.

--_icon-size: 2ch;
--_icon-color: var(--_accent);

Ombre du bouton

Pour que les ombres s'adaptent correctement à la lumière et au noir, elles doivent à la fois modifier leur couleur et leur opacité. Les ombres du thème clair sont idéales lorsqu'elles sont subtiles et teintées vers la couleur de la surface qu'elles superposent. Les ombres du thème sombre doivent être plus sombres et plus saturées pour pouvoir superposer des couleurs de surface plus sombres.

--_shadow-color-light: 220 3% 15%;
--_shadow-color-dark: 220 40% 2%;
--_shadow-color: var(--_shadow-color-light);

--_shadow-strength-light: 1%;
--_shadow-strength-dark: 25%;
--_shadow-strength: var(--_shadow-strength-light);

Grâce aux couleurs et aux intensités adaptatives, je peux obtenir deux profondeurs d'ombre:

--_shadow-1: 0 1px 2px -1px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 9%));

--_shadow-2: 
  0 3px 5px -2px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 3%)),
  0 7px 14px -5px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 5%));

De plus, pour donner aux boutons un aspect légèrement 3D, une ombre carrée 1px crée l'illusion:

--_shadow-depth-light: 0 1px var(--_border-light);
--_shadow-depth-dark: 0 1px var(--_bg-dark);
--_shadow-depth: var(--_shadow-depth-light);

Transitions de boutons

En suivant le modèle des couleurs adaptatives, je crée deux propriétés statiques pour contenir les options du système de conception:

--_transition-motion-reduce: ;
--_transition-motion-ok:
  box-shadow 145ms ease,
  outline-offset 145ms ease
;
--_transition: var(--_transition-motion-reduce);

Toutes les propriétés réunies dans le sélecteur

Toutes les propriétés personnalisées d'un sélecteur

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  --_accent-light: hsl(210 100% 40%);
  --_accent-dark: hsl(210 50% 70%);
  --_accent: var(--_accent-light);

--_text-light: hsl(210 10% 30%); --_text-dark: hsl(210 5% 95%); --_text: var(--_text-light);

--_bg-light: hsl(0 0% 100%); --_bg-dark: hsl(210 9% 31%); --_bg: var(--_bg-light);

--_input-well-light: hsl(210 16% 87%); --_input-well-dark: hsl(204 10% 10%); --_input-well: var(--_input-well-light);

--_padding-inline: 1.75ch; --_padding-block: .75ch;

--_border-radius: .5ch; --_border-light: hsl(210 14% 89%); --_border-dark: var(--_bg-dark); --_border: var(--_border-light);

--_highlight-size: 0; --_highlight-light: hsl(210 10% 71% / 25%); --_highlight-dark: hsl(210 10% 5% / 25%); --_highlight: var(--_highlight-light);

--_ink-shadow-light: 0 1px 0 hsl(210 14% 89%); --_ink-shadow-dark: 0 1px 0 hsl(210 11% 15%); --_ink-shadow: var(--_ink-shadow-light);

--_icon-size: 2ch; --_icon-color-light: var(--_accent-light); --_icon-color-dark: var(--_accent-dark); --_icon-color: var(--accent, var(--_icon-color-light));

--_shadow-color-light: 220 3% 15%; --_shadow-color-dark: 220 40% 2%; --_shadow-color: var(--_shadow-color-light); --_shadow-strength-light: 1%; --_shadow-strength-dark: 25%; --_shadow-strength: var(--_shadow-strength-light); --_shadow-1: 0 1px 2px -1px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 9%)); --_shadow-2: 0 3px 5px -2px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 3%)), 0 7px 14px -5px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 5%)) ;

--_shadow-depth-light: hsl(210 14% 89%); --_shadow-depth-dark: var(--_bg-dark); --_shadow-depth: var(--_shadow-depth-light);

--_transition-motion-reduce: ; --_transition-motion-ok: box-shadow 145ms ease, outline-offset 145ms ease ; --_transition: var(--_transition-motion-reduce); }

Les boutons par défaut s&#39;affichent côte à côte dans les thèmes clair et sombre.

Adaptations avec thème sombre

La valeur du modèle d'accessoires statiques -light et -dark devient claire lorsque les accessoires du thème sombre sont définis:

@media (prefers-color-scheme: dark) {
  :where(
    button,
    input[type="button"],
    input[type="submit"],
    input[type="reset"],
    input[type="file"]
  ),
  :where(input[type="file"])::file-selector-button {
    --_bg: var(--_bg-dark);
    --_text: var(--_text-dark);
    --_border: var(--_border-dark);
    --_accent: var(--_accent-dark);
    --_highlight: var(--_highlight-dark);
    --_input-well: var(--_input-well-dark);
    --_ink-shadow: var(--_ink-shadow-dark);
    --_shadow-depth: var(--_shadow-depth-dark);
    --_shadow-color: var(--_shadow-color-dark);
    --_shadow-strength: var(--_shadow-strength-dark);
  }
}

Non seulement cela se lit bien, mais les consommateurs de ces boutons personnalisés peuvent utiliser les accessoires simples en étant sûr qu'ils s'adapteront de manière appropriée aux préférences de l'utilisateur.

Adaptations de mouvement réduites

Si le mouvement convient à cet utilisateur visiteur, attribuez --_transition à var(--_transition-motion-ok):

@media (prefers-reduced-motion: no-preference) {
  :where(
    button,
    input[type="button"],
    input[type="submit"],
    input[type="reset"],
    input[type="file"]
  ),
  :where(input[type="file"])::file-selector-button {
    --_transition: var(--_transition-motion-ok);
  }
}

Quelques styles partagés

La police des boutons et des entrées doit être définie sur inherit afin qu'elles correspondent aux autres polices de la page. Sinon, elles seront stylisées par le navigateur. Cela s'applique également à letter-spacing. Si vous définissez line-height sur 1.5, la taille de la zone aux lettres est définie de manière à laisser un espace au-dessus et en dessous du texte:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  /* …CSS variables */

  font: inherit;
  letter-spacing: inherit;
  line-height: 1.5;
  border-radius: var(--_border-radius);
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Appliquer un style aux boutons

Ajustement du sélecteur

Le sélecteur input[type="file"] n'est pas la partie bouton de l'entrée, contrairement au pseudo-élément ::file-selector-button. J'ai donc supprimé input[type="file"] de la liste:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  
}

Réglages du curseur et des commandes tactiles

Tout d'abord, j'applique le style de curseur pointer au bouton, ce qui permet au bouton d'indiquer aux utilisateurs de souris qu'il est interactif. Ensuite, j'ajoute touch-action: manipulation pour éviter que les clics n'aient à attendre et pour observer un potentiel double-clic, ce qui rend les boutons plus rapides:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  cursor: pointer;
  touch-action: manipulation;
}

Couleurs et bordures

Ensuite, je personnalise la taille de la police, l'arrière-plan, le texte et les couleurs de bordure à l'aide de certaines des propriétés personnalisées adaptatives établies précédemment:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  …

  font-size: var(--_size, 1rem);
  font-weight: 700;
  background: var(--_bg);
  color: var(--_text);
  border: 2px solid var(--_border);
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Ombres

D'excellentes techniques ont été appliquées aux boutons. Le text-shadow s'adapte à la lumière et au sombre, ce qui crée une apparence subtile agréable du texte du bouton bien placé au-dessus de l'arrière-plan. Pour box-shadow, trois ombres sont attribuées. La première, --_shadow-2, est une ombre de rectangle standard. La deuxième ombre est une astuce à l'œil qui fait que le bouton semble légèrement plié. La dernière ombre correspond à la mise en surbrillance du passage de la souris, initialement avec une taille de 0, mais une taille lui sera attribuée par la suite et une transition sera effectuée de sorte qu'elle semble grandir à partir du bouton.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  …

  box-shadow: 
    var(--_shadow-2),
    var(--_shadow-depth),
    0 0 0 var(--_highlight-size) var(--_highlight)
  ;
  text-shadow: var(--_ink-shadow);
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Mise en page

J'ai défini une mise en page flexbox pour le bouton, en particulier une mise en page inline-flex adaptée à son contenu. Centrez ensuite le texte, et alignez les enfants verticalement et horizontalement au centre. Cela aidera les icônes et les autres éléments de bouton à s'aligner correctement.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  …

  display: inline-flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Espacement

Pour l'espacement des boutons, j'ai utilisé gap pour empêcher les frères d'appuyer sur l'écran et des propriétés logiques pour la marge intérieure afin que l'espacement des boutons fonctionne pour toutes les mises en page de texte.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  …

  gap: 1ch;
  padding-block: var(--_padding-block);
  padding-inline: var(--_padding-inline);
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Expérience utilisateur tactile et de la souris

La section suivante s'adresse principalement aux utilisateurs de l'écran tactile sur les appareils mobiles. La première propriété, user-select, s'adresse à tous les utilisateurs. Elle empêche la mise en surbrillance du texte du bouton. Cela est principalement visible sur les appareils tactiles lorsqu'un utilisateur appuie de manière prolongée sur un bouton et que le système d'exploitation met en surbrillance le texte du bouton.

Je trouve que ce n'est généralement pas l'expérience utilisateur avec les boutons dans les applications intégrées. Je la désactive donc en définissant user-select sur "Aucun". Les couleurs de mise en surbrillance (-webkit-tap-highlight-color) et les menus contextuels du système d'exploitation (-webkit-touch-callout) sont d'autres fonctionnalités très axées sur le Web qui ne correspondent pas aux attentes générales des utilisateurs. Je les supprime donc également.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  …

  user-select: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}

Transitions

La variable adaptative --_transition est attribuée à la propriété de transition:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  …

  transition: var(--_transition);
}

Lors du survol, alors que l'utilisateur n'appuie pas activement, ajustez la taille de la mise en surbrillance des ombres pour lui donner une belle apparence qui semble grandir à partir du bouton:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
):where(:not(:active):hover) {
  --_highlight-size: .5rem;
}

Lors de la mise au point, augmentez le décalage des contours de la mise au point à partir du bouton, en lui donnant également une belle apparence qui semble se développer à partir du bouton:

:where(button, input):where(:not(:active)):focus-visible {
  outline-offset: 5px;
}

Icônes

Pour gérer les icônes, le sélecteur dispose d'un sélecteur :where() ajouté pour les enfants SVG directs ou les éléments avec l'attribut personnalisé data-icon. La taille de l'icône est définie avec la propriété personnalisée à l'aide des propriétés logiques intégrées et de bloc. La couleur du trait est définie, ainsi qu'un élément drop-shadow correspondant à l'élément text-shadow. flex-shrink étant défini sur 0, l'icône n'est jamais écrasée. Enfin, je sélectionne des icônes lignées et j'attribue ces styles ici avec des limites de ligne et des jointures de ligne fill: none et round:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
) > :where(svg, [data-icon]) {
  block-size: var(--_icon-size);
  inline-size: var(--_icon-size);
  stroke: var(--_icon-color);
  filter: drop-shadow(var(--_ink-shadow));

  flex-shrink: 0;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Personnalisation des boutons d'envoi

Je voulais que les boutons d'envoi aient une apparence légèrement promue. J'ai obtenu ce résultat en faisant de la couleur du texte des boutons la couleur d'accentuation:

:where(
  [type="submit"], 
  form button:not([type],[disabled])
) {
  --_text: var(--_accent);
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Personnaliser les boutons de réinitialisation

Je voulais que les boutons de réinitialisation intègrent des panneaux d'avertissement pour alerter les utilisateurs de leur comportement potentiellement destructeur. J'ai également choisi de styliser le bouton du thème clair avec plus de touches de rouge que le thème sombre. La personnalisation se fait en modifiant la couleur claire ou sombre appropriée, et le bouton met à jour le style:

:where([type="reset"]) {
  --_border-light: hsl(0 100% 83%);
  --_highlight-light: hsl(0 100% 89% / 20%);
  --_text-light: hsl(0 80% 50%);
  --_text-dark: hsl(0 100% 89%);
}

J'ai également pensé qu'il serait intéressant que la couleur du contour de la mise au point corresponde à l'accentuation du rouge. La couleur du texte adapte un rouge foncé à un rouge clair. La couleur du contour correspond à celle du mot clé currentColor:

:where([type="reset"]):focus-visible {
  outline-color: currentColor;
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Personnaliser les boutons désactivés

Il est trop courant que les boutons désactivés présentent un faible contraste des couleurs lorsque l'utilisateur essaie de le supprimer, ce qui le rend moins actif. J'ai testé chaque jeu de couleurs et je me suis assuré qu'ils réussissaient, en encourageant la valeur de luminosité HSL jusqu'à ce que le score soit transmis dans les outils de développement ou dans VisBug.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
)[disabled] {
  --_bg: none;
  --_text-light: hsl(210 7% 40%);
  --_text-dark: hsl(210 11% 71%);

  cursor: not-allowed;
  box-shadow: var(--_shadow-1);
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Personnalisation des boutons de saisie de fichier

Le bouton d'entrée de fichier est un conteneur pour un segment et un bouton. CSS est capable de styliser un peu le conteneur d'entrée ainsi que le bouton imbriqué, mais pas l'intervalle. Le conteneur reçoit max-inline-size afin qu'il ne devienne pas plus grand que nécessaire, tandis que inline-size: 100% s'autorise à réduire et à ajuster des conteneurs plus petits qu'il ne l'est. La couleur d'arrière-plan est définie sur une couleur adaptative plus sombre que les autres surfaces. Elle se trouve donc derrière le bouton de sélection de fichier.

:where(input[type="file"]) {
  inline-size: 100%;
  max-inline-size: max-content;
  background-color: var(--_input-well);
}

Le bouton du sélecteur de fichier et les boutons du type d'entrée disposent de appearance: none pour supprimer tous les styles fournis par le navigateur qui n'ont pas été écrasés par les autres styles de bouton.

:where(input[type="button"]),
:where(input[type="file"])::file-selector-button {
  appearance: none;
}

Enfin, une marge est ajoutée à l'élément inline-end du bouton pour éloigner le texte du segment du bouton, ce qui crée de l'espace.

:where(input[type="file"])::file-selector-button {
  margin-inline-end: var(--_padding-inline);
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Exceptions spéciales liées au thème sombre

J'ai choisi un arrière-plan plus sombre pour les principaux boutons d'action afin d'accentuer le contraste du texte, afin de leur donner une apparence légèrement plus mise en avant.

@media (prefers-color-scheme: dark) {
  :where(
    [type="submit"],
    [type="reset"],
    [disabled],
    form button:not([type="button"])
  ) {
    --_bg: var(--_input-well);
  }
}

Capture d&#39;écran montrant des boutons après l&#39;application des styles précédents.

Créer des variantes

Pour le plaisir et pour des raisons pratiques, j'ai choisi de montrer comment créer quelques variantes. Une variante est très colorée, semblable à l'apparence souvent des boutons principaux. Une autre variante est "grande". La dernière variante présente une icône avec un dégradé.

Bouton coloré

Pour obtenir ce style de bouton, j'ai remplacé les accessoires de base directement par des couleurs bleues. Bien que cette méthode soit rapide et facile, elle supprime les accessoires adaptatifs et a le même aspect dans les thèmes clair et sombre.

.btn-custom {
  --_bg: linear-gradient(hsl(228 94% 67%), hsl(228 81% 59%));
  --_border: hsl(228 89% 63%);
  --_text: hsl(228 89% 100%);
  --_ink-shadow: 0 1px 0 hsl(228 57% 50%);
  --_highlight: hsl(228 94% 67% / 20%);
}

Le bouton personnalisé s&#39;affiche en clair et dans l&#39;obscurité. Il est bleu éclatant, comme les boutons d&#39;action principaux classiques.

Grand bouton

Ce style de bouton est obtenu en modifiant la propriété personnalisée --_size. La marge intérieure et les autres éléments d'espace sont relatifs à cette taille et sont ajustés proportionnellement à la nouvelle taille.

.btn-large {
  --_size: 1.5rem;
}

Le grand bouton est affiché à côté du bouton personnalisé, environ 150 fois plus grand.

Bouton d'icône

Cet effet d'icône n'a rien à voir avec nos styles de boutons, mais il montre comment y parvenir avec seulement quelques propriétés CSS, et comment le bouton gère les icônes qui ne sont pas au format SVG intégré.

[data-icon="cloud"] {
  --icon-cloud: url("https://api.iconify.design/mdi:apple-icloud.svg") center / contain no-repeat;

  -webkit-mask: var(--icon-cloud);
  mask: var(--icon-cloud);
  background: linear-gradient(to bottom, var(--_accent-dark), var(--_accent-light));
}

Un bouton avec une icône s&#39;affiche dans les thèmes clair et sombre.

Conclusion

Maintenant que vous savez comment je l'ai fait, comment le feriez-vous‽ 😃 ?

Diversissons nos approches et apprenons toutes les façons de créer sur le Web.

Créez une démo, cliquez sur les liens tweet me, et je l'ajouterai à la section "Remix" de la communauté ci-dessous.

Remix de la communauté

Rien à afficher pour le moment.

Ressources