Una descripción general básica de cómo compilar componentes <button> accesibles, responsivos y adaptables al color.
En esta entrada, quiero compartir mis ideas sobre cómo crear un elemento <button> adaptable al color, responsivo y accesible.
Probar la demostración y ver el código fuente
Si prefieres un video, aquí tienes una versión de este artículo en YouTube:
Descripción general
El elemento <button> se creó para la interacción del usuario. Su evento click se activa desde el teclado, el mouse, el tacto, la voz y mucho más, con reglas inteligentes sobre su sincronización. También incluye algunos estilos predeterminados en cada navegador, por lo que puedes usarlos directamente sin ninguna personalización. Usa color-scheme para habilitar también los botones de modo claro y oscuro proporcionados por 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á para 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 que ayudarán 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 color azul de acento para que parezcan un poco más destacados que los botones normales.
Los botones también tienen pseudoclases para que CSS las use en el diseño. Estas clases proporcionan enlaces 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 con el diseño de la tecnología de asistencia.
button:hover {}
button:active {}
button:focus {}
button:focus-visible {}
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 incorporado a SVG enmascarado, para garantizar 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 seudoclases y el hecho de estar 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
Los elementos de botón son accesibles de forma natural, pero existen algunas mejoras comunes.
Enfoque y desplazamiento juntos
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 de tecnología de asistencia.
button:is(:hover, :focus) {
…
}
Anillo de enfoque interactivo
Me gusta animar el anillo de enfoque para los usuarios de teclado y tecnologías de accesibilidad. Para lograr esto, animo el contorno para que se aleje del botón en 5 px, pero solo cuando el botón no está activo. Esto crea un efecto que hace que el anillo de enfoque se contraiga hasta el tamaño del botón cuando se presiona.
:where(button, input):where(:not(:active)):focus-visible {
outline-offset: 5px;
}
Cómo garantizar un contraste de color adecuado
Hay al menos cuatro combinaciones de colores diferentes entre el modo claro y el oscuro que requieren considerar 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 las puntuaciones a la vez:
Ocultar íconos para las personas que no pueden verlos
Cuando crees un botón de ícono, el ícono debe brindar asistencia visual al texto del botón. Esto también significa que el ícono no es valioso para alguien con pérdida de visión. Afortunadamente, 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 molesten con imágenes de botones decorativos:
<button>
<svg … aria-hidden="true">...</svg>
Icon Button
</button>
Estilos
En la siguiente sección, primero estableceré un sistema de propiedades personalizado para administrar los estilos adaptativos del botón. Con esas propiedades personalizadas, puedo comenzar a seleccionar elementos y personalizar su apariencia.
Una estrategia de propiedades personalizadas adaptativa
La estrategia de propiedades personalizadas que se usa en este desafío de GUI es muy similar a la que se usa en cómo compilar un esquema de color. Para un sistema de color adaptable claro y oscuro, se define una propiedad personalizada para cada tema y se le asigna el nombre correspondiente. Luego, se usa una sola propiedad personalizada para mantener el valor actual del tema y se asigna a una propiedad de CSS. Más adelante, la única propiedad personalizada se puede actualizar 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);
}
}
Lo que me gusta es que los temas claro y oscuro son 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áticas. También se puede leer claramente que el tema claro es el tema 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 segmentar todos los tipos de botones y, al principio, puede resultar un poco abrumador. Se usa :where(), por lo que no se requiere especificidad para personalizar el botón. Los botones suelen adaptarse a situaciones alternativas, y el selector :where() garantiza que la tarea sea sencilla. 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 definirá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 avancemos y, luego, compartiré los contextos de movimiento reducido y oscuro al final de la sección.
Color de acento 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 del texto de los botones no son blancos ni negros, sino versiones oscurecidas o aclaradas de --_accent con hsl() y respetando el 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 configuran en blanco para que su superficie los haga parecer cerca del usuario o delante de 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 se usa para que una superficie aparezca detrás de otras, lo que resulta útil para el fondo de la entrada de archivos:
--_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 la fuente. Esto se vuelve fundamental cuando los botones grandes pueden aumentar simplemente el font-size y las escalas de los botones de forma proporcional:
--_padding-inline: 1.75ch;
--_padding-block: .75ch;
Borde del botón
El radio del borde del botón se almacena en una propiedad personalizada para que la entrada de archivo coincida con los otros botones. Los colores del borde siguen el sistema de color adaptable 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 el 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 color adaptable. Más adelante en esta publicación, explicaremos cómo interactúan, pero, en última instancia, se usan para generar un efecto de 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 capa agradable de pulido en 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 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 los modos claro y oscuro, deben cambiar su color y opacidad. Las sombras del tema claro son mejores cuando son sutiles y tienen un tinte hacia el color de superficie que 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 los colores y las intensidades adaptativos, 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 una apariencia ligeramente en 3D, una 1px box-shadow 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 adaptativos, 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
: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); }

Adaptaciones del tema oscuro
El valor del patrón de propiedades estáticas -light y -dark se vuelve claro cuando se establecen las propiedades 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 las propiedades básicas con la confianza de que se adaptarán de manera adecuada a las preferencias del usuario.
Adaptaciones de movimiento reducido
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
Los botones y las entradas deben tener sus fuentes configuradas 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 se establece line-height en 1.5, se establece el tamaño de la caja de letras para darle al texto algo 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);
}

Cómo diseñar 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 del cursor y del tacto
Primero, le doy al cursor el estilo pointer, lo que ayuda al botón a indicar a los usuarios de mouse que es interactivo. Luego, agrego touch-action: manipulation para que los clics no tengan que 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 adaptativas 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);
}

Sombras
Los botones tienen algunas técnicas excelentes aplicadas. El 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 encuentra sobre el fondo. Para box-shadow, se asignan tres sombras. La primera, --_shadow-2, es una sombra de caja normal.
La segunda sombra es un truco visual que hace que el botón parezca estar biselado un poco. 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 realizará 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);
}

Diseño
Le di al botón un diseño de flexbox, específicamente un diseño de inline-flex que se ajuste a su contenido. Luego, centré el texto y alineé 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;
}

Espaciado
Para el espaciado entre botones, usé gap para evitar que los elementos hermanos se toquen y propiedades lógicas para el padding, de modo que el espaciado entre 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);
}

UX de mouse y táctil
La siguiente sección está dirigida 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 se destaque el texto del botón. Esto se nota más en los dispositivos táctiles cuando se presiona y mantiene presionado un botón, y el sistema operativo destaca el texto del botón.
Por lo general, descubrí que esta no es la experiencia del usuario con los botones en las apps integradas, por lo que la inhabilito configurando user-select como none. Los colores de resaltado al presionar (-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 se alinean con las expectativas generales de los usuarios de los botones, por lo que también las 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 --_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 botón sin presionarlo, ajusta el tamaño del resaltado de la sombra para darle una apariencia 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;
}
Al enfocar, aumenta el desplazamiento del contorno de enfoque desde el botón y, además, dale una apariencia 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 los elementos secundarios SVG directos o los elementos con el atributo personalizado data-icon. El tamaño del ícono se establece con la propiedad personalizada que usa 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 comprima. Por último, selecciono íconos lineales y les asigno esos estilos aquí con extremos y uniones 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;
}

Cómo personalizar los botones de envío
Quería que los botones de envío tuvieran una apariencia ligeramente destacada, y lo logré haciendo que el color del texto de los botones fuera el color de acento:
:where(
[type="submit"],
form button:not([type],[disabled])
) {
--_text: var(--_accent);
}

Cómo 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 decidí diseñar el botón del tema claro con más detalles en rojo que el del tema oscuro. La personalización se realiza cambiando el color subyacente claro u 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 coincidiera con el acento del rojo. El color del texto se adapta de un rojo oscuro a un rojo claro. Hago que el color del contorno coincida con este con la palabra clave currentColor:
:where([type="reset"]):focus-visible {
outline-color: currentColor;
}

Personaliza los botones inhabilitados
Es muy común que los botones inhabilitados tengan un contraste de color deficiente cuando se intenta atenuarlos para que parezcan menos activos. Probé cada conjunto de colores y me aseguré de que pasaran, ajustando el valor de luminosidad HSL hasta que la puntuación pasara 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);
}

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. El CSS puede aplicar un poco de estilo al contenedor de entrada y al botón anidado, pero no al elemento span. El contenedor recibe max-inline-size para que no crezca más de lo necesario, mientras que inline-size: 100% se encogerá y se ajustará a los contenedores más pequeños que él. El color de fondo se establece en un color adaptable 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);
}
Los botones de entrada y de selección de archivos reciben específicamente appearance: none para quitar cualquier estilo proporcionado por el navegador que no se haya anulado con 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 del intervalo del botón y crear un espacio.
:where(input[type="file"])::file-selector-button {
margin-inline-end: var(--_padding-inline);
}

Excepciones especiales del tema oscuro
Les di a los botones de acción principales un fondo más oscuro para que el texto tenga un mayor contraste, lo que les da una apariencia ligeramente más destacada.
@media (prefers-color-scheme: dark) {
:where(
[type="submit"],
[type="reset"],
[disabled],
form button:not([type="button"])
) {
--_bg: var(--_input-well);
}
}

Cómo crear variantes
Para divertirme y porque es práctico, decidí 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 relleno con un gradiente.
Botón vibrante
Para lograr este estilo de botón, reemplacé las propiedades base directamente con colores azules. Si bien esto fue rápido y sencillo, quita las propiedades adaptativas 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%);
}

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

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 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));
}
![]()
Conclusión
Ahora que sabes cómo lo hice, ¿cómo lo harías tú? 🙂
Diversifiquemos nuestros enfoques y aprendamos todas las formas de crear contenido en la Web.
Crea una demostración, envíame por Twitter los vínculos y la agregaré a la sección de remixes de la comunidad que se encuentra a continuación.
Remixes de la comunidad
Aún no hay nada para ver aquí.
Recursos
- Código fuente en GitHub