Cómo compilar un componente de botón

Descripción general fundamental de cómo compilar componentes de <button> que se adapten al color, sean responsivos y accesibles.

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

Se utiliza el teclado y el mouse para interactuar con los botones 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

  • 1
  • 12
  • 1
  • ≤4

Origen

El elemento <button> está diseñado para la interacción del usuario. Su evento click se activa desde el teclado, el mouse, el tacto, la voz y otras funciones, con reglas inteligentes sobre su tiempo. También incluye algunos estilos predeterminados en cada navegador, de modo que puedes usarlos directamente sin personalización. Usa color-scheme para habilitar también los botones claros y oscuros proporcionados por el navegador.

También hay diferentes tipos de botones, cada uno de los cuales se muestra en la incorporación anterior de Codepen. 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 la GUI de este mes, cada botón tendrá estilos para ayudar a diferenciar visualmente su intención. Los botones para restablecer tendrán colores de advertencia, ya que son destructivos, y los botones para enviar tendrán un texto de acento azul para que parezca un poco más promovido que los botones normales.

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

Los botones también tienen seudoclases para que las CSS las usen a fin de definir los diseños. Estas clases proporcionan hooks de CSS para personalizar la sensación del botón: :hover para cuando un mouse está sobre el botón, :active para cuando un mouse o teclado presiona y :focus o :focus-visible para ayudar con el estilo de 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 otro 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 realizar pruebas, cada botón se coloca dentro de un formulario. De esta manera, puedo asegurarme de que los diseños se actualicen de forma adecuada para el botón predeterminado, que se comporta como un botón de envío. También cambio la estrategia de íconos, de SVG intercalado a SVG enmascarado, para asegurarme de que ambos funcionen por igual.

<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 seudoclases y el hecho de estar dentro o fuera de una forma, hay más de 20 combinaciones de botones. Lo bueno es que CSS pueda ayudarnos a articular cada uno de ellos con claridad.

Accesibilidad

Los elementos de botones son accesibles de manera natural, pero hay algunas mejoras comunes.

Coloca el cursor sobre un elemento y enfoca

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

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 ello, puedo animar el contorno del botón 5 px alejando el contorno del botón, 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 asegurar el contraste de colores de paso

Existen al menos cuatro combinaciones de colores diferentes para 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 a personas que no pueden ver

Cuando se crea un botón de ícono, este debe proporcionar soporte visual para el texto del botón. Esto también significa que el ícono no es valioso para una persona con pérdida de vista. Por suerte, el navegador proporciona una forma de ocultar elementos de la tecnología del lector de pantalla para que las personas con pérdida de visión no se molesten con las imágenes de botones decorativos:

<button>
  <svg … aria-hidden="true">...</svg>
  Icon Button
</button>
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 configurado como verdadero.
Herramientas para desarrolladores de Chrome que muestran el árbol de accesibilidad del botón. El árbol ignora la imagen del botón porque tiene aria-hidden configurado 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 propiedades personalizadas adaptables

La estrategia de propiedades personalizadas que se usa en este desafío de la GUI es muy similar a la que se usa para crear un esquema de colores. En el caso de un sistema de colores claro y oscuro adaptable, se define una propiedad personalizada para cada tema y se le asigna un nombre correspondiente. Luego, se usa una sola propiedad personalizada para conservar el valor actual del tema y se asigna a una propiedad de CSS. Luego, la propiedad personalizada única se puede actualizar a un valor diferente y, luego, se actualiza el diseño 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);
  }
}

Lo que me gusta es que los temas claro y oscuro son declarativos y claros. La indirecta 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áticas. También está claro que el tema claro es el predeterminado y que el oscuro solo se aplica de forma condicional.

Cómo prepararse para la coherencia del diseño

El selector compartido

El siguiente selector se usa para apuntar a todos los tipos de botones y es un poco abrumador al principio. Se usa :where(), por lo que la personalización del botón no requiere especificidad. Los botones a menudo se adaptan para 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 se determinarán dentro de este selector. Es hora de revisar todas las propiedades personalizadas. Hay varias propiedades personalizadas que se usan en este botón. Describiré cada grupo a medida que avanzamos y, luego, compartiré los contextos oscuros y reducidos de movimientos al final de la sección.

Color de los elementos destacados del botón

Los botones y los íconos de envío son ideales para 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 del texto de los botones no son blanco ni negro. Son versiones oscurecidas o aclaradas de --_accent que usan hsl() y se ajustan 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 para el Tema claro: se configuran 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);

Botón en segundo plano

Este color de fondo sirve para hacer que una superficie aparezca detrás de otras superficies, lo que es útil para el fondo de la entrada del 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 del botón se realiza con la unidad ch, una longitud relativa al tamaño de fuente. Esto resulta fundamental cuando los botones grandes pueden aumentar el font-size y el botón se escala proporcionalmente:

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

Borde del botón

El radio del borde del botón se almacena de forma temporal en una propiedad personalizada para que la entrada del archivo pueda coincidir con los otros botones. Los colores de los bordes siguen el sistema de colores adaptables 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 al colocar el cursor sobre un botón

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 adaptables. Más adelante en esta publicación, veremos cómo interactúan estos elementos, 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 pulido 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 nuevamente, lo que ayudará a que el ícono se escale proporcionalmente al texto del botón. El color del ícono se basa en el --_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 se ajustan al color de superficie con el que se superponen. Las sombras del tema oscuro deben ser más oscuras y saturadas para que puedan superponerse a los 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 las intensidades y los colores adaptables, puedo armar 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 una apariencia ligeramente 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 los 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 uno al lado del otro en el tema claro y oscuro.

Adaptaciones con temas oscuros

El valor de los patrones de los objetos estáticos -light y -dark queda claro cuando se configuran los props 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 es legible, sino que los consumidores de estos botones personalizados pueden usar los objetos básicos con la confianza de que se adaptarán de forma adecuada a las preferencias del usuario.

Adaptaciones de movimiento reducidas

Si el movimiento está bien con el 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 estar configuradas en inherit para que coincidan con el resto de las fuentes de la página. De lo contrario, el navegador les dará estilo. Esto también se aplica a letter-spacing. Si configuras line-height en 1.5, se establece el tamaño del cuadro de letras para que el texto tenga 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 estilo a los botones

Ajuste del selector

El selector input[type="file"] no es la parte del botón de la entrada, sino el seudoelemento ::file-selector-button, 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, diseño el cursor según 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 necesiten esperar y observar un posible clic doble, lo que hace que los botones parezcan 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 antes:

: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 geniales. El objeto text-shadow se adapta a la luz y a la oscuridad, lo que crea una apariencia agradable y sutil del texto del botón que se ubica muy bien sobre el fondo. Para el box-shadow, se asignan tres sombras. La primera, --_shadow-2, es una sombra de cuadro normal. La segunda sombra es un truco para el ojo que hace que el botón parezca estar un poco biselado. La última sombra es para el elemento destacado que se coloca cuando se coloca el cursor, inicialmente en un tamaño de 0, pero se le dará un tamaño más tarde y se hará una transición para que parezca crecer 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, en particular, un diseño inline-flex que se ajusta a su contenido. Luego, centré 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 los 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

En cuanto al espaciado de los botones, usé gap para evitar que los elementos del mismo nivel toquen y propiedades lógicas para el padding, de modo que el espaciado de botones funcione en 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 del panel táctil y del mouse

La siguiente sección está destinada principalmente a usuarios táctiles en dispositivos móviles. La primera propiedad, user-select, es para todos los usuarios y evita que se destaque el texto del botón. Esto se puede notar mayormente 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, descubrí que esta no es la experiencia del usuario con los botones de las apps integradas, así que la inhabilito estableciendo user-select en ninguno. Los colores de resaltado de elementos presionados (-webkit-tap-highlight-color) y los menús contextuales del sistema operativo (-webkit-touch-callout) son otras funciones de botones centradas en la Web que no están alineadas con las expectativas generales del usuario sobre los botones, por lo que también los quito.

: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 adaptable --_transition se asigna a la propiedad de transición:

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

  transition: var(--_transition);
}

Cuando el usuario coloque el cursor sobre un elemento, mientras el usuario no esté presionando de forma activa, ajusta el tamaño de la sombra que se destaca para darle una buena apariencia de enfoque que parezca crecer desde el botón:

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

Al enfocar, aumenta el desplazamiento del contorno del enfoque desde el botón, lo que también da una apariencia de enfoque agradable que parezca crecer desde el botón:

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

Íconos

Para controlar íconos, el selector tiene agregado un selector :where() para elementos 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 establecen el color del trazo y un elemento drop-shadow para que coincida con el elemento text-shadow. flex-shrink se configura como 0, por lo que el ícono nunca se aprieta. Por último, selecciono los íconos con líneas y asigno esos estilos aquí con los vértices de línea y las uniones de líneas 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.

Personalización de los botones de envío

Quería enviar botones para que tuvieran un aspecto ligeramente promovido, y logré que el color del texto de los botones fuera el color de los elementos destacados:

: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.

Personalizar 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 elegí diseñar el botón de Tema claro con más detalles en rojo que el Tema oscuro. Para realizar la personalización, cambia el color subyacente claro u oscuro, 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 foco coincidiera con el acento de rojo. El color del texto adapta un rojo oscuro a un 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.

Personalizar 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 fueran correctos, lo que sugirió el valor de luminosidad de HSL hasta que la puntuación se pasara en Herramientas para desarrolladores 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 un contenedor de un intervalo y un botón. CSS puede diseñar un poco el contenedor de entrada y el botón anidado, pero no el intervalo. El contenedor recibe max-inline-size para que no se expanda de lo necesario, mientras que inline-size: 100% permite reducirse y ajustarse a contenedores más pequeños de lo que es. El color de fondo se establece en un color adaptable 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 proporcionan específicamente 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 margen al inline-end del botón para alejar el texto del intervalo del botón, lo que crea un poco 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 al tema oscuro

Le di a los botones de acción principales un fondo más oscuro para que el texto contraste más, lo que les da un aspecto un poco más promovido.

@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.

Crea variantes

Por diversión, y debido a que es práctico, elegí mostrar cómo crear algunas variantes. Una variante es muy vibrante, similar a la apariencia de los botones principales. Otra variante es grande. La última variante tiene un ícono lleno de gradientes.

Botón vibrante

Para lograr este estilo de botón, sobrescribí los objetos básicos directamente con colores azules. Si bien fue rápido y fácil, quita los accesorios 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 tonos claros y oscuros. Es de color azul muy intenso, como suelen ser los botones de acción principales típicos.

Botón grande

Este estilo de botón se logra modificando la propiedad personalizada --_size. El padding y otros elementos del espacio están relacionados con este tamaño y se ajustan de manera proporcional al tamaño nuevo.

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

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

Botón de ícono

Este efecto de ícono no tiene nada que ver con nuestros diseños de botón, pero muestra cómo lograrlo con solo algunas propiedades de CSS y qué tan bien 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));
}

Un botón con un ícono se muestra en los temas claro y oscuro.

Conclusión

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

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

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

Remixes de la comunidad

Aún no hay nada que ver aquí.

Recursos