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 esta publicación en YouTube:

Descripción general

Navegadores compatibles

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

Origen

El elemento <button> está diseñado 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 su tiempo. 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 de restablecimiento tendrán colores de advertencia, ya que son destructivos, y los botones de envío 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 para que CSS las use para aplicar estilos. 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 asegurarme de que los estilos se actualicen de forma adecuada para el botón predeterminado, que se comporta como un botón de envío. 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 pseudoselector 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. Aquí se usa VisBug para inspeccionar y mostrar todas sus puntuaciones a la vez:

Cómo ocultar íconos para las personas que no pueden verlos

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>
Chrome DevTools muestra el árbol de accesibilidad del botón. 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 establezco 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 apariencia.

Una estrategia de propiedad personalizada adaptativa

La estrategia de propiedades personalizadas que se usa en este desafío de GUI es muy similar a la que se usa para crear un esquema de colores. Para un sistema de colores claros y oscuros adaptables, se define una propiedad personalizada para cada tema y se le asigna un nombre según corresponda. 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 segmentar todos los tipos de botones y puede ser 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 ::file-selector-button, que no se puede usar dentro de :is() o :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 se incluirán en 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 avancemos y, luego, compartiré los contextos de movimiento oscuro y reducido al final de la sección.

Color de los elementos destacados del botón

Los íconos y botones 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 de 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);

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 de botones

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 se vuelve fundamental cuando los botones grandes pueden aumentar el font-size y las escalas de botones de manera 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 del botón cuando se coloca el cursor sobre él

Estas propiedades establecen una propiedad de tamaño para la transición en la interacción, y el color de resaltado sigue el sistema de colores adaptable. Más adelante en esta publicación, hablaremos sobre 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 de longitud relativa ch, que ayudará a que el ícono se ajuste proporcionalmente al texto del botón. El color del ícono se basa en --_accent-color para obtener 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 tienen un tono similar al color de la superficie sobre la 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 se lee bien, sino que los consumidores de estos botones personalizados pueden usar los elementos sin procesar con la confianza de que se adaptarán de forma adecuada a las preferencias del usuario.

Adaptaciones de movimiento reducidas

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 de los botones y las entradas deben establecerse en inherit para que coincidan con el resto de las fuentes de la página. De lo contrario, el navegador les aplicará un diseño. 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 pseudoelemento ::file-selector-button es, 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 del cursor y del tacto

Primero, le doy estilo al cursor con el estilo pointer, lo 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

Los botones tienen algunas técnicas excelentes aplicadas. 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 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 de flexbox, específicamente un diseño inline-flex que se ajuste 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 los botones, usé gap para evitar que los elementos hermanos se toquen y propiedades lógicas para el padding, de modo que el espaciado de los 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 los dispositivos táctiles cuando se presiona y 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 establece en 0 para que el ícono nunca se reduzca. 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 que muestra 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é convirtiéndolos en el color de los botones de texto:

: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 personalizarlo, cambia el color subyacente claro o oscuro adecuado, y el botón actualizará el estilo:

: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 de enfoque coincida con el acento de rojo. El color del texto pasa de rojo oscuro a rojo claro. Hago que el color del contorno coincida con esto 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.

Cómo personalizar los botones inhabilitados

Es muy común que los botones inhabilitados tengan un contraste de color deficiente durante el intento de atenuar 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 archivo es un contenedor para un elemento span 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. 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 se asignan específicamente a appearance: none para quitar los estilos proporcionados por el navegador que no se reemplazaron por los otros estilos de botones.

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

Por último, se agrega un margen al inline-end del botón para alejar el texto de span del botón y crear un 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 brillante

Para lograr este estilo de botón, reemplacé los elementos básicos directamente con colores azules. Si bien esto fue rápido y sencillo, quita los elementos adaptativos 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 un azul muy intenso, como suelen ser los botones de acción principales típicos.

Botón grande

Para lograr este estilo de botón, se debe modificar 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 lo bien que el botón controla 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 tú? 🙂

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