Créer un composant de bouton

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

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

Les boutons sont utilisés via 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 cet article:

Présentation

Navigateurs pris en charge

  • Chrome : 1.
  • Edge : 12.
  • Firefox : 1.
  • Safari : 1.

Source

L'élément <button> est conçu pour l'interaction utilisateur. Ses déclencheurs d'événements click sont issus du clavier, de la souris, de l'écran tactile, de la voix, etc., avec des règles intelligentes sur le timing. Il est également fourni avec des styles par défaut dans chaque navigateur, ce qui vous permet de 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 étant illustré dans l'intégration Codepen précédente. Un élément <button> sans type s'adapte à un élément <form> et devient le 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 d'interface utilisateur de ce mois, chaque bouton recevra des styles pour aider à différencier visuellement leur intention. Étant donné qu'elles sont destructrices, les boutons de réinitialisation seront accompagnés d'une couleur d'avertissement. Les boutons d'envoi seront quant à eux accentués en bleu et paraîtront donc un peu plus mis en avant que les boutons standards.

Aperçu de l&#39;ensemble final de tous les types de boutons, affichés dans un formulaire et en dehors d&#39;un formulaire, avec de belles additions pour les boutons avec 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 en dehors d'un formulaire, avec de belles additions pour les boutons avec icône et les boutons personnalisés

Les boutons disposent également de pseudo-classes que le CSS peut utiliser pour le style. Ces classes fournissent des hooks CSS pour personnaliser l'apparence du bouton: :hover pour le passage de la souris au-dessus du bouton, :active lorsque l'utilisateur appuie sur une souris ou un clavier, et :focus ou :focus-visible pour faciliter la stylisation des technologies 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 dans le thème sombre

Majoration

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, pour les tests, chaque bouton est placé dans un formulaire. De cette façon, je peux m'assurer que les styles sont mis à jour de manière appropriée pour le bouton par défaut, qui se comporte comme un bouton de soumission. Je change également la stratégie d'icône, 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 des combinaisons est assez écrasante à ce stade. Il existe plus de 20 combinaisons de boutons entre les types de boutons, les pseudo-classes et le fait d'être dans un formulaire ou en dehors. Heureusement, le CSS peut nous aider à articuler chacun d'eux clairement.

Accessibilité

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

Pointer et mettre au point ensemble

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

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

Anneau de focus interactif

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

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

S'assurer que le contraste des couleurs est satisfaisant

Il existe au moins quatre combinaisons de couleurs différentes entre les couleurs claires et sombres qui doivent être prises en compte pour le contraste des couleurs : bouton, bouton de validation, bouton de réinitialisation et bouton désactivé. VisBug est utilisé ici pour inspecter et afficher tous leurs scores à la fois:

Masquer les icônes des personnes qui ne peuvent pas les voir

Lorsque vous créez un bouton avec icône, l'icône doit renforcer visuellement le texte du bouton. Cela signifie également que l'icône n'est pas utile pour une personne ayant une perte de vision. Heureusement, le navigateur permet de masquer des éléments à la technologie de lecteur d'écran afin que les personnes ayant une déficience visuelle ne soient pas gênées par les images de boutons décoratives :

<button>
  <svg … aria-hidden="true">...</svg>
  Icon Button
</button>
Outils pour les développeurs Chrome affichant l&#39;arborescence d&#39;accessibilité du bouton. L&#39;arborescence ignore l&#39;image du bouton, car la valeur aria-hidden est définie sur &quot;true&quot;.
Les outils pour les développeurs Chrome affichant l'arborescence d'accessibilité du bouton. L'arborescence ignore l'image du bouton, car l'attribut aria-hidden est défini sur "true".

Styles

Dans la section suivante, je vais d'abord établir un système de propriétés personnalisées pour gérer les styles adaptatifs du bouton. Avec ces propriétés personnalisées, je peux commencer à sélectionner des éléments et personnaliser leur apparence.

Une stratégie de propriété personnalisée adaptative

La stratégie de propriété personnalisée utilisée dans ce défi 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 pour chaque thème est définie et nommée en conséquence. Une seule propriété personnalisée est ensuite utilisée pour contenir la valeur actuelle du thème et attribuée à une propriété CSS. Par la suite, vous pouvez remplacer la valeur de la propriété personnalisée unique par une autre, puis mettre à jour le style du bouton.

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);
  }
}

J'apprécie que les thèmes clair et sombre soient déclaratifs et clairs. L'indirection et l'abstraction sont transférées vers 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 le thème sombre n'est appliqué que de manière conditionnelle.

Préparer la cohérence de la conception

Sélecteur de partage

Le sélecteur suivant permet de cibler tous les différents types de boutons. Il peut sembler un peu intimidant au premier abord. :where() est utilisé, de sorte que la personnalisation du bouton ne nécessite aucune spécificité. Les boutons sont souvent adaptés à d'autres scénarios, et le sélecteur :where() garantit cette tâche facile. 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 définies dans ce sélecteur. Passons maintenant en revue toutes les propriétés personnalisées. De nombreuses propriétés personnalisées sont utilisées dans ce bouton. Je vais décrire chaque groupe au fur et à mesure, puis partager les contextes sombres et à mouvement réduit à la fin de la section.

Couleur d'accentuation du bouton

Les boutons et icônes de soumission sont un excellent endroit 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 blanches ni noires. Il s'agit de versions plus sombres ou plus claires de --_accent à l'aide de hsl() et en respectant 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

L'arrière-plan des boutons suit le même schéma hsl(), à l'exception des boutons avec le thème clair. Ceux-ci sont définis en blanc afin que leur surface les fasse apparaître près de l'utilisateur ou devant d'autres surfaces:

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

Puits d'arrière-plan du bouton

Cette couleur d'arrière-plan permet d'afficher une surface derrière d'autres surfaces, ce qui est utile pour l'arrière-plan de la saisie de fichier :

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

Marge intérieure des boutons

L'espacement autour du texte du bouton est effectué à l'aide de l'unité ch, une longueur relative à la taille de la police. Cela devient essentiel lorsque les gros boutons peuvent simplement faire monter la font-size et que le bouton s'adapte de manière proportionnelle:

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

Bordure du bouton

L'arrondi de 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 surlignage du bouton au survol

Ces propriétés établissent une propriété de taille pour la transition lors de l'interaction, et la couleur de surlignage suit le système de couleurs adaptatif. Nous verrons comment ils interagissent plus loin dans cet article, mais ils sont finalement 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 est associé à un style d'ombre de texte subtil. Cela permet au texte de s'asseoir au-dessus du bouton, ce qui améliore la lisibilité et ajoute une belle couche de raffinement de la 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, ce qui permet de les redimensionner proportionnellement au texte du bouton. La couleur de l'icône s'appuie sur --_accent-color pour une couleur adaptative et dans le thème.

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

Ombre du bouton

Pour que les ombres s'adaptent correctement à la lumière et à l'obscurité, elles doivent modifier à la fois leur couleur et leur opacité. Les ombres du thème clair sont optimales 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 afin de pouvoir se superposer aux 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 assembler deux profondeurs d'ombres :

--_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 de boîte 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 du thème sombre

La valeur du modèle d'objet statique -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 la lecture est fluide, mais les utilisateurs de ces boutons personnalisés peuvent utiliser les accessoires bruts en toute confiance, car ils s'adapteront de manière appropriée aux préférences des utilisateurs.

Adaptations de mouvement réduites

Si l'utilisateur invité accepte le mouvement, 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 en commun

La police des boutons et des entrées doit être définie sur inherit afin qu'elles correspondent aux autres polices de la page. Dans le cas contraire, elles seront stylisées par le navigateur. Cela s'applique également à letter-spacing. Si line-height est défini sur 1.5, la taille de la boîte aux lettres est définie pour laisser un espace au texte au-dessus et en dessous:

: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 les 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 {
  
}

Ajustements du curseur et des commandes tactiles

Je commence par définir le style du curseur sur le style pointer, ce qui permet au bouton d'indiquer aux utilisateurs de la souris qu'il est interactif. J'ajoute ensuite touch-action: manipulation pour que les clics n'aient pas à attendre et à observer un double-clic potentiel, 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

Je personnalise ensuite la taille de la police, ainsi que les couleurs de l'arrière-plan, du texte et des bordures à 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 les boutons après l&#39;application des styles précédents.

Ombres

D'excellentes techniques sont appliquées aux boutons. text-shadow s'adapte à la lumière et à l'obscurité, créant une apparence subtile et agréable du texte du bouton sur l'arrière-plan. Pour box-shadow, trois ombres sont attribuées. La première, --_shadow-2, est une ombre portée de boîte standard. La deuxième ombre est un trompe-l'œil qui donne l'impression que le bouton est légèrement biseauté vers le haut. La dernière ombre est destinée à l'accentuation en survol. Elle a initialement une taille de 0, mais elle en recevra une plus tard et sera mise en transition pour qu'elle semble croître à 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 les boutons après l&#39;application des styles précédents.

Mise en page

J'ai attribué au bouton une mise en page flexbox, en particulier une mise en page inline-flex adaptée à son contenu. Ensuite, je centre le texte, puis j'aligne verticalement et horizontalement les enfants sur le centre. Cela permet d'aligner correctement les icônes et les autres éléments de bouton.

: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 les boutons après l&#39;application des styles précédents.

Espacement

Pour l'espacement des boutons, j'ai utilisé gap pour éviter que les frères et sœurs ne se touchent 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 les boutons après l&#39;application des styles précédents.

Expérience utilisateur avec les commandes tactiles et la souris

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

En règle générale, ce n'est pas l'expérience utilisateur avec les boutons dans les applications intégrées. Je le désactive donc en définissant user-select sur "none". Les couleurs de surbrillance des boutons (-webkit-tap-highlight-color) et les menus contextuels de l'OS (-webkit-touch-callout) sont d'autres fonctionnalités de bouton très axées sur le Web qui ne correspondent pas aux attentes générales des utilisateurs concernant les boutons. 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 --_transition adaptative est attribuée à la propriété transition :

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

  transition: var(--_transition);
}

Lorsque l'utilisateur pointe sur le bouton, sans appuyer dessus, ajustez la taille de l'ombre pour lui donner un aspect de mise au point qui semble croître depuis le bouton :

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

Lorsque l'utilisateur est en mode "Focus", augmentez le décalage du contour de sélection par rapport au bouton, ce qui lui donne une belle apparence de sélection qui semble croître à 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 ajoute un sélecteur :where() 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 de propriétés logiques en ligne et en bloc. La couleur du trait est définie, ainsi qu'un élément drop-shadow correspondant à l'élément text-shadow. flex-shrink est défini sur 0, de sorte que l'icône ne soit jamais écrasée. Enfin, je sélectionne les icônes avec des traits et j'attribue ces styles ici avec les extrémités et les raccordements 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 les boutons après l&#39;application des styles précédents.

Personnaliser les boutons d'envoi

Je voulais que les boutons d'envoi aient une apparence légèrement mise en avant. Pour ce faire, j'ai défini la couleur du texte des boutons sur la couleur d'accentuation :

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

Capture d&#39;écran montrant les 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 d'accents rouges que le thème sombre. La personnalisation se fait en modifiant la couleur sous-jacente 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 aussi pensé qu'il serait bon que la couleur du contour du focus corresponde à l'accent du rouge. La couleur du texte adapte le rouge foncé au rouge clair. Je définis la couleur du contour comme suit avec le mot clé currentColor:

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

Capture d&#39;écran montrant les 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 aient un faible contraste de couleurs lors de la tentative d'atténuer le bouton désactivé afin qu'il apparaisse moins actif. J'ai testé chaque ensemble de couleurs et m'assurer qu'ils ont réussi, en ajustant la valeur de luminosité HSL jusqu'à ce que le score soit accepté dans DevTools ou 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 les boutons après l&#39;application des styles précédents.

Personnaliser les boutons de saisie de fichiers

Le bouton d'importation de fichier est un conteneur d'une span et d'un bouton. Le CSS peut styliser un peu le conteneur d'entrée, ainsi que le bouton imbriqué, mais pas la span. Le conteneur est défini sur max-inline-size afin qu'il ne devienne pas plus grand que nécessaire, tandis que inline-size: 100% lui permet de se réduire et de s'adapter aux conteneurs plus petits que lui. La couleur d'arrière-plan est définie sur une couleur adaptative plus sombre que les autres surfaces, de sorte qu'elle se trouve derrière le bouton de sélecteur de fichiers.

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

Le bouton de sélecteur de fichiers et les boutons de type de saisie sont spécifiquement définis sur appearance: none pour supprimer tous les styles fournis par le navigateur qui n'ont pas été écrasés par les autres styles de boutons.

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

Enfin, une marge est ajoutée à l'inline-end du bouton pour éloigner le texte span du bouton, créant ainsi un espace.

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

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

Exceptions liées au thème sombre

J'ai donné aux boutons d'action principaux un arrière-plan plus sombre pour un contraste plus élevé du texte, ce qui leur donne un aspect légèrement plus mis 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 les boutons après l&#39;application des styles précédents.

Créer des variantes

Pour m'amuser et parce que c'est pratique, j'ai choisi de montrer comment créer quelques variantes. L'une des variantes est très vive, comme le sont souvent les boutons principaux. Une autre variante est volumineuse. La dernière variante présente une icône avec un dégradé.

Bouton coloré

Pour obtenir ce style de bouton, j'ai écrasé les propriétés de base directement avec des couleurs bleues. Bien que l'opération ait été simple et rapide, elle permet de supprimer les accessoires adaptatifs et de présenter la même apparence 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 en mode sombre. Il est bleu très vif, comme le sont généralement les boutons d&#39;action principaux.

Bouton de grande taille

Pour obtenir ce style de bouton, modifiez la propriété personnalisée --_size. Le rembourrage et les autres éléments d'espace sont relatifs à cette taille, et sont mis à l'échelle proportionnellement à la nouvelle taille.

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

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

Bouton en forme d'icône

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

[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 j'ai fait, comment feriez-vous ? 😃

Diversifiez nos approches et découvrons toutes les manières de créer des applications sur le Web.

Créez une démo, tweetez-moi des liens et je les ajouterai à la section "Remix de la communauté" ci-dessous.

Remix de la communauté

Aucun élément à afficher pour le moment.

Ressources