Cómo compilar un componente de botón

Una descripción general fundamental de cómo compilar componentes <button> accesibles, responsivos y adaptables al color.

En esta publicación, quiero compartir mis ideas sobre cómo crear un elemento <button> accesible, responsivo y adaptable al color. Prueba la demostración y consulta la fuente.

Se puede interactuar con los botones mediante el teclado y el mouse en los temas claro y oscuro.

Si prefieres ver un video, aquí tienes una versión de YouTube de esta publicación:

Descripción general

Navegadores compatibles

  • Chrome: 1.
  • Límite: 12.
  • Firefox: 1.
  • Safari: 1.

Origen

El elemento <button> se diseñó para la interacción del usuario. Sus activadores de eventos click se activan desde el teclado, el mouse, la función táctil, la voz y mucho más, con reglas inteligentes sobre los tiempos. También incluye algunos estilos predeterminados en cada navegador, por lo que puedes usarlos directamente sin ninguna personalización. Usa color-scheme para habilitar los botones claros y oscuros que proporciona el navegador.

También hay diferentes tipos de botones, cada uno de los cuales se muestra en la incorporación de Codepen anterior. Un <button> sin un tipo se adaptará a estar dentro de un <form> y cambiará al tipo de envío.

<!-- 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">

En el Desafío de GUI de este mes, cada botón tendrá estilos para ayudar a diferenciar visualmente su intención. Los botones Restablecer tendrán colores de advertencia, ya que son destructivos, y los botones Enviar tendrán texto de acento azul para que aparezcan un poco más destacados que los botones normales.

Vista previa del conjunto final de todos los tipos de botones, que se muestran en un formulario y no en un formulario, con buenas incorporaciones para botones de íconos y botones personalizados.
Vista previa del conjunto final de todos los tipos de botones, que se muestran en un formulario y no en un formulario, con excelentes incorporaciones para botones de íconos y botones personalizados

Los botones también tienen pseudoclases que CSS puede usar para definir el diseño. Estas clases proporcionan hooks de CSS para personalizar la apariencia del botón: :hover para cuando el mouse está sobre el botón, :active para cuando se presiona el mouse o el teclado, y :focus o :focus-visible para ayudar en el diseño de la tecnología de accesibilidad.

button:hover {}
button:active {}
button:focus {}
button:focus-visible {}
Vista previa del conjunto final de todos los tipos de botones en el tema oscuro.
Vista previa del conjunto final de todos los tipos de botones en el tema oscuro

Marca

Además de los tipos de botones que proporciona la especificación HTML, agregué un botón con un ícono y un botón con una clase personalizada 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">

Luego, para las pruebas, cada botón se coloca dentro de un formulario. De esta manera, puedo garantizar que los diseños se actualicen de forma adecuada para el botón predeterminado, que se comporta como un botón de enviar. También cambié la estrategia de íconos, de SVG intercalado a SVG enmascarado, para asegurarme de que ambos funcionen igual de 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 matriz de combinaciones es bastante abrumadora en este punto. Entre los tipos de botones, las pseudoclases y la ubicación dentro o fuera de un formulario, hay más de 20 combinaciones de botones. Es bueno que CSS pueda ayudarnos a articular cada uno de ellos con claridad.

Accesibilidad

Se puede acceder a los elementos de botón de forma natural, pero hay algunas mejoras comunes.

Coloca el cursor y enfoca juntos

Me gusta agrupar :hover y :focus junto con el seudoselector funcional :is(). Esto ayuda a garantizar que mis interfaces siempre tengan en cuenta los estilos del teclado y la tecnología de accesibilidad.

button:is(:hover, :focus) {
  
}
¡Prueba una demostración!

Anillo de enfoque interactivo

Me gusta animar el anillo de enfoque para los usuarios de teclado y tecnología de accesibilidad. Para lograr esto, animé el contorno para que se aleje del botón 5 px, pero solo cuando el botón no está activo. Esto crea un efecto que hace que el anillo de enfoque se reduzca al tamaño del botón cuando se presiona.

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

Cómo garantizar que se apruebe el contraste de color

Hay al menos cuatro combinaciones de colores diferentes entre claro y oscuro que deben tener en cuenta el contraste de color: botón, botón de envío, botón de restablecimiento y botón inhabilitado. VisBug se usa aquí para inspeccionar y mostrar todas sus puntuaciones a la vez:

Ocultar iconos de las personas que no pueden ver

Cuando crees un botón de ícono, este debe brindar compatibilidad visual con el texto del botón. Esto también significa que el ícono no es valioso para una persona con pérdida de visión. Por suerte, el navegador proporciona una forma de ocultar elementos de la tecnología de lectores de pantalla para que las personas con pérdida de visión no se vean afectadas por las imágenes decorativas de los botones:

<button>
  <svg … aria-hidden="true">...</svg>
  Icon Button
</button>
Se muestra el árbol de accesibilidad del botón para las Herramientas para desarrolladores de Chrome. El árbol ignora la imagen del botón porque tiene aria-hidden establecido como verdadero.
Las Herramientas para desarrolladores de Chrome muestran el árbol de accesibilidad del botón. El árbol ignora la imagen del botón porque tiene aria-hidden establecido como verdadero.

Estilos

En la siguiente sección, primero estableceré un sistema de propiedades personalizadas para administrar los estilos adaptables del botón. Con esas propiedades personalizadas, puedo comenzar a seleccionar elementos y personalizar su aspecto.

Una estrategia de propiedad personalizada adaptativa

La estrategia de propiedades personalizadas que se usa en este desafío de la GUI es muy similar a la que se usa para compilar un esquema de colores. En el caso de un sistema adaptable de colores claros y oscuros, se define una propiedad personalizada para cada tema y se le asigna el nombre correspondiente. Luego, se usa una sola propiedad personalizada para contener el valor actual del tema y se asigna a una propiedad CSS. Más adelante, se puede actualizar la única propiedad personalizada a un valor diferente y, luego, actualizar el estilo del botón.

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

Me gusta que los temas claro y oscuro sean declarativos y claros. La indirección y la abstracción se descargan en la propiedad personalizada --_bg, que ahora es la única propiedad "reactiva"; --_bg-light y --_bg-dark son estáticos. También se indica claramente que el tema claro es el predeterminado y que el tema oscuro solo se aplica de forma condicional.

Preparación para la coherencia del diseño

El selector compartido

El siguiente selector se usa para apuntar a todos los diferentes tipos de botones y es un poco abrumador al principio. Se usa :where() para que la personalización del botón no requiera especificidad. Los botones a menudo se adaptan a situaciones alternativas, y el selector :where() garantiza que la tarea sea fácil. Dentro de :where(), se selecciona cada tipo de botón, incluido el ::file-selector-button, que no se puede usar dentro de :is() ni :where().

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

Todas las propiedades personalizadas tendrán alcance dentro de este selector. Es hora de revisar todas las propiedades personalizadas. Hay bastantes propiedades personalizadas que se usan en este botón. Describiré cada grupo a medida que avanzamos y luego compartiré los contextos oscuro y de movimiento reducido al final de la sección.

Color de los elementos destacados del botón

Los botones y los íconos de envío son un excelente lugar para agregar un toque de color:

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

Color del texto del botón

Los colores de texto de los botones no son blancos ni negros, son versiones oscurecidas o aclaradas de --_accent con hsl() y se adhieren al tono 210:

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

Color del botón de la parte inferior

Los fondos de los botones siguen el mismo patrón hsl(), excepto los botones del tema claro, que se establecen en blanco para que su superficie los haga aparecer cerca del usuario o frente a otras superficies:

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

Bien el fondo del botón

Este color de fondo sirve para hacer que una superficie aparezca detrás de otras, lo que es útil para el fondo de la entrada de archivo:

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

Relleno del botón

El espaciado alrededor del texto en el botón se realiza con la unidad ch, una longitud relativa al tamaño de la fuente. Esto es fundamental cuando los botones grandes pueden aumentar el font-size y los botones se escalan de forma proporcional:

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

Borde del botón

El radio del borde del botón se oculta en una propiedad personalizada para que la entrada de archivo pueda coincidir con los otros botones. Los colores de los bordes siguen el sistema de colores adaptativos establecido:

--_border-radius: .5ch;

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

Efecto de resaltado cuando se coloca el cursor sobre un botón

Estas propiedades establecen una propiedad de tamaño para la transición durante la interacción, y el color de resaltado sigue el sistema de colores adaptables. Más adelante en esta publicación, explicaremos cómo interactúan, pero, en última instancia, se usan para un efecto box-shadow:

--_highlight-size: 0;

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

Sombra del texto del botón

Cada botón tiene un estilo de sombra de texto sutil. Esto ayuda a que el texto se ubique sobre el botón, lo que mejora la legibilidad y agrega una buena capa de perfeccionamiento de la presentación.

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

Ícono de botón

Los íconos tienen el tamaño de dos caracteres gracias a la unidad ch de longitud relativa, lo que ayudará a que el ícono se ajuste de forma proporcional al texto del botón. El color del ícono se basa en el --_accent-color para brindar un color adaptable y dentro del tema.

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

Sombra del botón

Para que las sombras se adapten correctamente a la luz y la oscuridad, deben cambiar su color y opacidad. Las sombras del tema claro son mejores cuando son sutiles y se tiñen hacia el color de la superficie que se superponen. Las sombras del tema oscuro deben ser más oscuras y más saturadas para que puedan superponerse con colores de superficie más oscuros.

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

Con colores y fortalezas adaptables, puedo ensamblar dos profundidades de sombras:

--_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%));

Además, para darles a los botones un aspecto ligeramente en 3D, una sombra de cuadro 1px crea la ilusión:

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

Transiciones de botones

Siguiendo el patrón de colores adaptables, creo dos propiedades estáticas para contener las opciones del sistema de diseño:

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

Todas las propiedades juntas en el selector

Todas las propiedades personalizadas de un selector

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

Los botones predeterminados se muestran en el tema claro y oscuro en paralelo.

Adaptaciones del tema oscuro

El valor del patrón de elementos estáticos -light y -dark se aclara cuando se configuran los elementos del tema oscuro:

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

Esto no solo lee bien, sino que los consumidores de estos botones personalizados pueden usar los accesorios básicos con la confianza de que se adaptarán de manera adecuada a las preferencias del usuario.

Adaptaciones de movimiento reducidos

Si el movimiento es aceptable para este usuario visitante, asigna --_transition a 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);
  }
}

Algunos estilos compartidos

Las fuentes y los botones deben tener el valor inherit para que coincidan con el resto de las fuentes de la página; de lo contrario, el navegador les aplicará un estilo. Esto también se aplica a letter-spacing. Si estableces line-height en 1.5, se establece el tamaño del cuadro de letras para darle al texto un poco de espacio arriba y abajo:

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

Captura de pantalla que muestra los botones después de aplicar los estilos anteriores.

Cómo aplicar diseño a los botones

Ajuste del selector

El selector input[type="file"] no es la parte del botón de la entrada, el seudoelemento ::file-selector-button sí, por lo que quité input[type="file"] de la lista:

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

Ajustes táctiles y del cursor

Primero, cambio el estilo del cursor al estilo pointer, que ayuda al botón a indicar a los usuarios del mouse que es interactivo. Luego, agrego touch-action: manipulation para que los clics no deban esperar y observar un posible doble clic, lo que hace que los botones se sientan más rápidos:

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

Colores y bordes

A continuación, personalizo el tamaño de la fuente, el fondo, el texto y los colores del borde con algunas de las propiedades personalizadas adaptables establecidas anteriormente:

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

Captura de pantalla que muestra los botones después de aplicar los estilos anteriores.

Sombras

Se aplican algunas técnicas excelentes a los botones. text-shadow se adapta a la luz y la oscuridad, lo que crea una apariencia sutil y agradable del texto del botón que se ubica bien sobre el fondo. Para el box-shadow, se asignan tres sombras. El primero, --_shadow-2, es una sombra de cuadro normal. La segunda sombra es un truco visual que hace que el botón parezca biselado un poco hacia arriba. La última sombra es para el resaltado de desplazamiento, que inicialmente tiene un tamaño de 0, pero se le asignará un tamaño más adelante y se le hará una transición para que parezca que crece desde el botón.

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

Captura de pantalla que muestra los botones después de aplicar los estilos anteriores.

Diseño

Le di al botón un diseño flexbox, específicamente un diseño inline-flex que se ajustará a su contenido. Luego, centro el texto y alineo los elementos secundarios de forma vertical y horizontal en el centro. Esto ayudará a que los íconos y otros elementos de botones se alineen correctamente.

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

Captura de pantalla que muestra los botones después de aplicar los estilos anteriores.

Espaciado

Para el espaciado de botones, usé gap para evitar que los elementos del mismo nivel toquen, y las propiedades lógicas para el padding, de modo que el espaciado de botones funcione para todos los diseños de texto.

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

Captura de pantalla que muestra los botones después de aplicar los estilos anteriores.

UX táctil y de mouse

La siguiente sección está destinada principalmente a los usuarios de dispositivos móviles con pantalla táctil. La primera propiedad, user-select, es para todos los usuarios y evita que el texto destaque el texto del botón. Esto se nota principalmente en dispositivos táctiles cuando se presiona y se mantiene presionado un botón, y el sistema operativo destaca el texto del botón.

En general, he descubierto que esta no es la experiencia del usuario con los botones en las apps integradas, por lo que lo inhabilito configurando user-select como ninguno. Los colores de resaltado (-webkit-tap-highlight-color) y los menús contextuales del sistema operativo (-webkit-touch-callout) son otras funciones de botones muy centradas en la Web que no están alineadas con las expectativas generales de los usuarios de botones, por lo que también los quité.

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

Transiciones

La variable --_transition adaptable se asigna a la propiedad transition:

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

  transition: var(--_transition);
}

Cuando el usuario coloca el cursor sobre el elemento, mientras no lo presiona de forma activa, ajusta el tamaño del resaltado de sombra para darle un aspecto de enfoque agradable que parezca crecer desde el interior del botón:

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

Cuando se enfoque, aumenta el desplazamiento del contorno de enfoque desde el botón y dale un aspecto de enfoque agradable que parezca crecer desde el interior del botón:

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

Íconos

Para controlar los íconos, el selector tiene un selector :where() agregado para elementos secundarios SVG directos o elementos con el atributo personalizado data-icon. El tamaño del ícono se establece con la propiedad personalizada mediante propiedades lógicas intercaladas y de bloque. Se establece el color del trazo, así como un drop-shadow para que coincida con el text-shadow. flex-shrink se configura como 0 para que el ícono nunca se aplaste. Por último, selecciono los íconos con líneas y les asigno esos estilos aquí con las líneas de unión y los finales de línea fill: none y 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;
}

Captura de pantalla en la que se muestran los botones después de aplicar los estilos anteriores.

Personaliza los botones de envío

Quería que los botones de envío tuvieran una apariencia ligeramente destacada, y lo logré mediante el color de texto de los botones como color de contraste:

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

Captura de pantalla que muestra los botones después de aplicar los estilos anteriores.

Personaliza los botones de restablecimiento

Quería que los botones de restablecimiento tuvieran algunas señales de advertencia integradas para alertar a los usuarios sobre su comportamiento potencialmente destructivo. También decidí aplicar diseño al botón del tema claro con más acentos rojos que el tema oscuro. Para realizar la personalización, se cambia el color subyacente claro u oscuro adecuado, y el botón actualizará el diseño:

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

También pensé que sería bueno que el color del contorno del enfoque coincidiera con el acento del rojo. El color del texto pasa de rojo oscuro a rojo claro. Hago que el color del contorno coincida con la palabra clave currentColor:

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

Captura de pantalla que muestra los botones después de aplicar los estilos anteriores.

Personalizar botones inhabilitados

Es muy común que los botones inhabilitados tengan un contraste de color deficiente cuando se intenta suavizar el botón inhabilitado para que parezca menos activo. Probé cada conjunto de colores y me aseguré de que aprobaran, ajustando el valor de luminosidad de HSL hasta que la puntuación se aprobó en DevTools o 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);
}

Captura de pantalla que muestra los botones después de aplicar los estilos anteriores.

Cómo personalizar los botones de entrada de archivos

El botón de entrada de archivos es el contenedor de un intervalo y un botón. CSS puede darle un poco de estilo al contenedor de entrada, así como al botón anidado, pero no al intervalo. Se le asigna max-inline-size al contenedor para que no crezca más de lo necesario, mientras que inline-size: 100% se contraerá y se ajustará a contenedores más pequeños que él. El color de fondo se establece en un color adaptativo que es más oscuro que otras superficies, por lo que se ve detrás del botón del selector de archivos.

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

El botón del selector de archivos y los botones de tipo de entrada reciben específicamente appearance: none para quitar cualquier diseño proporcionado por el navegador que no haya sido reemplazado por los otros estilos de botones.

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

Por último, se agrega el margen al inline-end del botón para alejar el texto del intervalo del botón, lo que crea algo de espacio.

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

Captura de pantalla que muestra los botones después de aplicar los estilos anteriores.

Excepciones especiales del tema oscuro

Les di a los botones de acción principales un fondo más oscuro para lograr un contraste de texto más alto, lo que les da una apariencia un poco más destacada.

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

Captura de pantalla que muestra los botones después de aplicar los estilos anteriores.

Cómo crear variantes

Por diversión y porque es práctico, decidí mostrar cómo crear algunas variantes. Una variante es muy vibrante, similar a la apariencia que suelen tener los botones principales. Otra variante es grande. La última variante tiene un ícono relleno con gradiente.

Botón vibrante

Para lograr este estilo de botón, reemplacé los atributos base directamente con colores azules. Si bien esto fue rápido y sencillo, quita los elementos de diseño adaptables y se ve igual en los temas claro y oscuro.

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

El botón personalizado se muestra en colores claro y oscuro. Es de color azul muy intenso, como suelen tener los botones de acción principal.

Botón grande

Este estilo de botón se logra modificando la propiedad personalizada --_size. El padding y otros elementos de espacio son relativos a este tamaño y se ajustan proporcionalmente con el nuevo tamaño.

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

El botón grande se muestra junto al botón personalizado, aproximadamente 150 veces más grande.

Botón de ícono

Este efecto de ícono no tiene nada que ver con nuestros estilos de botones, pero muestra cómo lograrlo con solo algunas propiedades de CSS y qué tan bien el botón maneja los íconos que no son SVG intercalados.

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

Se muestra un botón con un ícono en temas claros y oscuros.

Conclusión

Ahora que sabes cómo lo hice, ¿cómo lo harías?‽ 🙂

Diversifiquemos nuestros enfoques y aprendamos todas las formas de compilar en la Web.

Crea una demo, twittea los vínculos y los agregaré a la sección de remixes de la comunidad a continuación.

Remixes de la comunidad

Aún no hay nada que ver.

Recursos