Descripción general básica de cómo compilar un componente de configuración de controles deslizantes y casillas de verificación.
En esta publicación, quiero compartir ideas sobre cómo crear un componente de configuración para la Web que sea adaptable, admita múltiples entradas de dispositivos y funcione en todos los navegadores. Prueba la demostración.
Si prefieres un video o quieres una vista previa de la IU/UX de lo que estamos creando, aquí tienes un recorrido más corto en YouTube:
Descripción general
Desglosé los aspectos de este componente en las siguientes secciones:
- Diseños
- Color
- Entrada de rango personalizado
- Entrada de casilla de verificación personalizada
- Consideraciones sobre accesibilidad
- JavaScript
Diseños
Esta es la primera demostración del Desafío de GUI que es completamente de cuadrícula CSS. A continuación, se muestra cada cuadrícula destacada con las Herramientas para desarrolladores de Chrome para cuadrículas:
Solo para el espacio
El diseño más común es el siguiente:
foo {
display: grid;
gap: var(--something);
}
Llamo a este diseño "solo para espacios" porque solo usa la cuadrícula para agregar espacios entre los bloques.
Cinco diseños usan esta estrategia. Aquí se muestran todos:
El elemento fieldset
, que contiene cada grupo de entrada (.fieldset-item
), usa gap: 1px
para crear los bordes finos entre los elementos. Sin soluciones complicadas para los bordes
.grid { display: grid; gap: 1px; background: var(--bg-surface-1); & > .fieldset-item { background: var(--bg-surface-2); } }
.grid { display: grid; & > .fieldset-item { background: var(--bg-surface-2); &:not(:last-child) { border-bottom: 1px solid var(--bg-surface-1); } } }
Ajuste natural de la cuadrícula
El diseño más complejo resultó ser el diseño macro, el sistema de diseño lógico entre <main>
y <form>
.
Cómo centrar el contenido de ajuste
Flexbox y la cuadrícula proporcionan capacidades para align-items
o align-content
, y, cuando se trata de elementos de ajuste, las alineaciones de diseño de content
distribuirán el espacio entre los elementos secundarios como un grupo.
main {
display: grid;
gap: var(--space-xl);
place-content: center;
}
El elemento principal usa la notación abreviada de alineación place-content: center
para que los elementos secundarios se centren vertical y horizontalmente en los diseños de una y dos columnas.
En el video anterior, observa cómo el "contenido" permanece centrado, incluso después de que se produjo el ajuste.
Repite el método del minimax de ajuste automático
<form>
usa un diseño de cuadrícula adaptable para cada sección.
Este diseño cambia de una a dos columnas según el espacio disponible.
form {
display: grid;
gap: var(--space-xl) var(--space-xxl);
grid-template-columns: repeat(auto-fit, minmax(min(10ch, 100%), 35ch));
align-items: flex-start;
max-width: 89vw;
}
Esta cuadrícula tiene un valor diferente para row-gap
(--space-xl) que para column-gap
(--space-xxl) para darle ese toque personalizado al diseño responsivo. Cuando las columnas se apilan, queremos un espacio grande, pero no tan grande como si estuviéramos en una pantalla ancha.
La propiedad grid-template-columns
usa 3 funciones de CSS: repeat()
, minmax()
y min()
. Una Kravets tiene una excelente entrada de blog sobre el diseño en la que habla de esto y lo llama RAM.
Si comparas nuestro diseño con el de Una, verás que hay 3 elementos especiales adicionales:
- Pasamos una función
min()
adicional. - Especificamos
align-items: flex-start
. - Hay un estilo
max-width: 89vw
.
Evan Minto describe bien la función min()
adicional en su blog, en la publicación Intrinsically Responsive CSS Grid with minmax() and min(). Te recomiendo que la leas. La corrección de alineación flex-start
sirve para quitar el efecto de estiramiento predeterminado, de modo que los elementos secundarios de este diseño no necesiten tener la misma altura, sino que puedan tener alturas naturales e intrínsecas. En el video de YouTube, se explica brevemente cómo se agregó esta alineación.
max-width: 89vw
merece un pequeño desglose en esta publicación.
Te mostraré el diseño con y sin el estilo aplicado:
¿Qué sucede? Cuando se especifica max-width
, se proporciona contexto, tamaño explícito o tamaño definido para que el algoritmo de diseño de auto-fit
sepa cuántas repeticiones puede colocar en el espacio. Si bien parece obvio que el espacio es de "ancho completo", según la especificación de la cuadrícula CSS, se debe proporcionar un tamaño definido o un tamaño máximo. Proporcioné un tamaño máximo.
Entonces, ¿por qué 89vw
? Porque "funcionó" para mi diseño.
Yo y otros compañeros de Chrome estamos investigando por qué un valor más razonable, como 100vw
, no es suficiente y si se trata de un error.
Espaciado
La mayor parte de la armonía de este diseño proviene de una paleta limitada de espaciado, 7 para ser exactos.
:root {
--space-xxs: .25rem;
--space-xs: .5rem;
--space-sm: 1rem;
--space-md: 1.5rem;
--space-lg: 2rem;
--space-xl: 3rem;
--space-xxl: 6rem;
}
El uso de estos flujos funciona muy bien con la cuadrícula, CSS @nest y la sintaxis de nivel 5 de @media. Este es un ejemplo del conjunto de diseños de <main>
completamente definido.
main {
display: grid;
gap: var(--space-xl);
place-content: center;
padding: var(--space-sm);
@media (width >= 540px) {
& {
padding: var(--space-lg);
}
}
@media (width >= 800px) {
& {
padding: var(--space-xl);
}
}
}
Una cuadrícula con contenido centrado y relleno moderado de forma predeterminada (como en dispositivos móviles). Sin embargo, a medida que hay más espacio disponible en la ventana gráfica, se extiende aumentando el padding. El CSS del 2021 se ve bastante bien.
¿Recuerdas el diseño anterior, "solo para la brecha"? A continuación, se muestra una versión más completa de cómo se ven en este componente:
header {
display: grid;
gap: var(--space-xxs);
}
section {
display: grid;
gap: var(--space-md);
}
Color
El uso controlado del color ayudó a que este diseño se destacara como expresivo y minimalista. Yo lo hago de la siguiente manera:
:root {
--surface1: lch(10 0 0);
--surface2: lch(15 0 0);
--surface3: lch(20 0 0);
--surface4: lch(25 0 0);
--text1: lch(95 0 0);
--text2: lch(75 0 0);
}
Nombro mis colores de superficie y texto con números en lugar de nombres como surface-dark
y surface-darker
porque, en una consulta de medios, los invertiré, y claro y oscuro no tendrán significado.
Las cambio en una consulta de medios de preferencia de la siguiente manera:
:root {
...
@media (prefers-color-scheme: light) {
& {
--surface1: lch(90 0 0);
--surface2: lch(100 0 0);
--surface3: lch(98 0 0);
--surface4: lch(85 0 0);
--text1: lch(20 0 0);
--text2: lch(40 0 0);
}
}
}
Es importante echar un vistazo rápido a la estrategia y la imagen general antes de profundizar en los detalles de la sintaxis de color. Pero, como me adelanté un poco, retrocedamos.
¿LCH?
Sin profundizar demasiado en la teoría del color, LCH es una sintaxis orientada a los humanos que se adapta a cómo percibimos el color, no a cómo lo medimos con matemáticas (como 255). Esto le da una ventaja distintiva, ya que los humanos pueden escribirlo con mayor facilidad y otros humanos estarán en sintonía con estos ajustes.

En esta demostración, nos enfocaremos en la sintaxis y los valores que cambio para crear el modo claro y oscuro. Veamos 1 color de superficie y 1 color de texto:
:root {
--surface1: lch(10 0 0);
--text1: lch(95 0 0);
@media (prefers-color-scheme: light) {
& {
--surface1: lch(90 0 0);
--text1: lch(40 0 0);
}
}
}
--surface1: lch(10 0 0)
se traduce en una luminosidad de 10%
, una croma de 0 y un tono de 0: un gris muy oscuro y sin color. Luego, en la consulta de medios para el modo claro, la luminosidad se invierte a 90%
con --surface1: lch(90 0 0);
. Esa es la esencia de la estrategia. Comienza por cambiar la luminosidad entre los 2 temas, manteniendo las relaciones de contraste que requiere el diseño o lo que puede mantener la accesibilidad.
La ventaja de usar lch()
aquí es que el brillo está orientado a los humanos, y podemos sentirnos bien con un cambio de %
en él, ya que será perceptualmente y de manera coherente ese %
diferente. hsl()
, por ejemplo, no es tan confiable.
Si te interesa, puedes obtener más información sobre los espacios de color y lch()
. ¡Ya llega!
Actualmente, CSS no puede acceder a estos colores. Permítanme repetir: No tenemos acceso a un tercio de los colores en la mayoría de los monitores modernos. Y no son colores cualquiera, sino los más vívidos que la pantalla puede mostrar. Nuestros sitios web se ven descoloridos porque el hardware de los monitores evolucionó más rápido que las especificaciones de CSS y las implementaciones de los navegadores.
Lea Verou
Controles de formulario adaptables con color-scheme
Muchos navegadores incluyen controles de tema oscuro, como Safari y Chromium, pero debes especificar en CSS o HTML que tu diseño los usa.
En la imagen anterior, se muestra el efecto de la propiedad del panel Styles de Herramientas para desarrolladores. En la demostración, se usa la etiqueta HTML, que, en mi opinión, suele ser una mejor ubicación:
<meta name="color-scheme" content="dark light">
Obtén más información en este color-scheme
artículo de Thomas Steiner. Hay mucho más que ganar que entradas de casillas de verificación oscuras.
CSS accent-color
Hubo actividad reciente en torno a accent-color
en los elementos de formulario, ya que es un solo estilo CSS que puede cambiar el color de tinte que se usa en el elemento de entrada del navegador. Obtén más información aquí en GitHub. La incluí en los estilos de este componente. A medida que los navegadores lo admitan, mis casillas de verificación se adaptarán más al tema con los toques de color rosa y morado.
input[type="checkbox"] {
accent-color: var(--brand);
}
Color pops con gradientes fijos y enfoque interno
El color destaca más cuando se usa con moderación, y una de las formas en que me gusta lograrlo es a través de interacciones de IU coloridas.
En el video anterior, hay muchas capas de comentarios y de interacción de la IU que ayudan a darle personalidad a la interacción de las siguientes maneras:
- Contexto destacado.
- Proporcionar comentarios de la IU sobre "qué tan completo" está el valor en el rango
- Proporcionar comentarios de la IU que indiquen que un campo acepta entrada
Para proporcionar comentarios cuando se interactúa con un elemento, CSS usa la pseudoclase :focus-within
para cambiar la apariencia de varios elementos. Analicemos .fieldset-item
, es muy interesante:
.fieldset-item {
...
&:focus-within {
background: var(--surface2);
& svg {
fill: white;
}
& picture {
clip-path: circle(50%);
background: var(--brand-bg-gradient) fixed;
}
}
}
Cuando uno de los elementos secundarios de este elemento tiene focus-within:
- El fondo
.fieldset-item
tiene asignado un color de superficie de mayor contraste. - El
svg
anidado se rellena de color blanco para un mayor contraste. - El
<picture>
clip-path
anidado se expande hasta formar un círculo completo y el fondo se rellena con el gradiente fijo brillante.
Período personalizado
Dado el siguiente elemento de entrada HTML, te mostraré cómo personalicé su apariencia:
<input type="range">
Este elemento tiene 3 partes que debemos personalizar:
Estilos de elementos de rango
input[type="range"] {
/* style setting variables */
--track-height: .5ex;
--track-fill: 0%;
--thumb-size: 3ex;
--thumb-offset: -1.25ex;
--thumb-highlight-size: 0px;
appearance: none; /* clear styles, make way for mine */
display: block;
inline-size: 100%; /* fill container */
margin: 1ex 0; /* ensure thumb isn't colliding with sibling content */
background: transparent; /* bg is in the track */
outline-offset: 5px; /* focus styles have space */
}
Las primeras líneas de CSS son las partes personalizadas de los estilos, y espero que etiquetarlas claramente ayude. El resto de los estilos son, en su mayoría, estilos de restablecimiento, para proporcionar una base coherente para crear las partes complejas del componente.
Estilos de segmentos
input[type="range"]::-webkit-slider-runnable-track {
appearance: none; /* clear styles, make way for mine */
block-size: var(--track-height);
border-radius: 5ex;
background:
/* hard stop gradient:
- half transparent (where colorful fill we be)
- half dark track fill
- 1st background image is on top
*/
linear-gradient(
to right,
transparent var(--track-fill),
var(--surface1) 0%
),
/* colorful fill effect, behind track surface fill */
var(--brand-bg-gradient) fixed;
}
El truco consiste en "revelar" el color de relleno vibrante. Esto se hace con el gradiente de detención brusca en la parte superior. El gradiente es transparente hasta el porcentaje de relleno y, después de eso, usa el color de la superficie de la pista sin rellenar. Detrás de esa superficie sin relleno, hay un color de ancho completo que espera que la transparencia lo revele.
Estilo de relleno de la pista
Mi diseño sí requiere JavaScript para mantener el estilo de relleno. Existen estrategias solo con CSS, pero requieren que el elemento de control tenga la misma altura que el riel, y no pude encontrar una armonía dentro de esos límites.
/* grab sliders on page */
const sliders = document.querySelectorAll('input[type="range"]')
/* take a slider element, return a percentage string for use in CSS */
const rangeToPercent = slider => {
const max = slider.getAttribute('max') || 10;
const percent = slider.value / max * 100;
return `${parseInt(percent)}%`;
};
/* on page load, set the fill amount */
sliders.forEach(slider => {
slider.style.setProperty('--track-fill', rangeToPercent(slider));
/* when a slider changes, update the fill prop */
slider.addEventListener('input', e => {
e.target.style.setProperty('--track-fill', rangeToPercent(e.target));
})
})
Creo que esto genera una buena actualización visual. El control deslizante funciona muy bien sin JavaScript, la propiedad --track-fill
no es obligatoria, simplemente no tendrá un estilo de relleno si no está presente. Si JavaScript está disponible, completa la propiedad personalizada y observa los cambios del usuario, sincronizando la propiedad personalizada con el valor.
Aquí tienes una excelente publicación de Ana Tudor en CSS-Tricks que muestra una solución solo con CSS para el relleno de pistas. También me resultó muy inspirador este elemento range
.
Estilos de miniaturas
input[type="range"]::-webkit-slider-thumb {
appearance: none; /* clear styles, make way for mine */
cursor: ew-resize; /* cursor style to support drag direction */
border: 3px solid var(--surface3);
block-size: var(--thumb-size);
inline-size: var(--thumb-size);
margin-top: var(--thumb-offset);
border-radius: 50%;
background: var(--brand-bg-gradient) fixed;
}
La mayoría de estos estilos sirven para crear un círculo agradable.
Una vez más, verás el gradiente de fondo fijo que unifica los colores dinámicos de los pulgares, los segmentos y los elementos SVG asociados.
Separé los estilos de la interacción para ayudar a aislar la técnica de box-shadow
que se usa para el resaltado al pasar el cursor:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
::-webkit-slider-thumb {
…
/* shadow spread is initally 0 */
box-shadow: 0 0 0 var(--thumb-highlight-size) var(--thumb-highlight-color);
/* if motion is OK, transition the box-shadow change */
@media (--motionOK) {
& {
transition: box-shadow .1s ease;
}
}
/* on hover/active state of parent, increase size prop */
@nest input[type="range"]:is(:hover,:active) & {
--thumb-highlight-size: 10px;
}
}
El objetivo era crear un elemento visual animado y fácil de administrar para destacar los comentarios de los usuarios. Con una sombra de caja, puedo evitar activar el diseño con el efecto. Para ello, creo una sombra que no está desenfocada y que coincide con la forma circular del elemento de control deslizante. Luego, cambio y hago la transición del tamaño de propagación cuando se coloca el cursor sobre el elemento.
Si tan solo el efecto de resaltado fuera tan fácil en las casillas de verificación…
Selectores de varios navegadores
Descubrí que necesitaba estos selectores -webkit-
y -moz-
para lograr la coherencia entre navegadores:
input[type="range"] {
&::-webkit-slider-runnable-track {}
&::-moz-range-track {}
&::-webkit-slider-thumb {}
&::-moz-range-thumb {}
}
Casilla de verificación personalizada
Dado el siguiente elemento de entrada HTML, te mostraré cómo personalicé su apariencia:
<input type="checkbox">
Este elemento tiene 3 partes que debemos personalizar:
Elemento de casilla de verificación
input[type="checkbox"] {
inline-size: var(--space-sm); /* increase width */
block-size: var(--space-sm); /* increase height */
outline-offset: 5px; /* focus style enhancement */
accent-color: var(--brand); /* tint the input */
position: relative; /* prepare for an absolute pseudo element */
transform-style: preserve-3d; /* create a 3d z-space stacking context */
margin: 0;
cursor: pointer;
}
Los diseños transform-style
y position
preparan el seudoelemento que presentaremos más adelante para diseñar el resaltado. De lo contrario, son principalmente cuestiones de estilo menores y subjetivas. Me gusta que el cursor sea un puntero, me gustan los desplazamientos del contorno, las casillas de verificación predeterminadas son demasiado pequeñas y, si accent-color
es compatible, incorpora estas casillas de verificación al esquema de color de la marca.
Etiquetas de casillas de verificación
Es importante proporcionar etiquetas para las casillas de verificación por 2 motivos. El primero es representar para qué se usa el valor de la casilla de verificación, para responder "¿activado o desactivado para qué?". El segundo es para la UX: los usuarios web se acostumbraron a interactuar con las casillas de verificación a través de sus etiquetas asociadas.
<input type="checkbox" id="text-notifications" name="text-notifications" >
<label for="text-notifications"> <h3>Text Messages</h3> <small>Get notified about all text messages sent to your device</small> </label>
En tu etiqueta, coloca un atributo for
que apunte a una casilla de verificación por ID: <label for="text-notifications">
. En tu casilla de verificación, duplica el nombre y el ID para asegurarte de que se encuentre con diferentes herramientas y tecnologías, como un mouse o un lector de pantalla: <input type="checkbox" id="text-notifications" name="text-notifications">
.
:hover
, :active
y muchos más se incluyen de forma gratuita con la conexión, lo que aumenta las formas en que se puede interactuar con tu formulario.
Destacado de casilla de verificación
Quiero que mis interfaces sean coherentes, y el elemento deslizador tiene un buen resaltado de miniatura que me gustaría usar con la casilla de verificación. La miniatura pudo usar box-shadow
y su propiedad spread
para escalar una sombra hacia arriba y hacia abajo. Sin embargo, ese efecto no funciona aquí porque nuestras casillas de verificación son cuadradas y deberían serlo.
Pude lograr el mismo efecto visual con un pseudoelemento y una cantidad desafortunada de CSS complicado:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
input[type="checkbox"]::before {
--thumb-scale: .01; /* initial scale of highlight */
--thumb-highlight-size: var(--space-xl);
content: "";
inline-size: var(--thumb-highlight-size);
block-size: var(--thumb-highlight-size);
clip-path: circle(50%); /* circle shape */
position: absolute; /* this is why position relative on parent */
top: 50%; /* pop and plop technique (https://web.dev/centering-in-css#5-pop-and-plop) */
left: 50%;
background: var(--thumb-highlight-color);
transform-origin: center center; /* goal is a centered scaling circle */
transform: /* order here matters!! */
translateX(-50%) /* counter balances left: 50% */
translateY(-50%) /* counter balances top: 50% */
translateZ(-1px) /* PUTS IT BEHIND THE CHECKBOX */
scale(var(--thumb-scale)) /* value we toggle for animation */
;
will-change: transform;
@media (--motionOK) { /* transition only if motion is OK */
& {
transition: transform .2s ease;
}
}
}
/* on hover, set scale custom property to "in" state */
input[type="checkbox"]:hover::before {
--thumb-scale: 1;
}
Crear un seudoelemento de círculo es un trabajo sencillo, pero colocarlo detrás del elemento al que está adjunto fue más difícil. Aquí puedes ver el antes y el después de la corrección:
Definitivamente, es una microinteracción, pero es importante para mí mantener la coherencia visual. La técnica de ajuste de escala de la animación es la misma que hemos usado en otros lugares. Establecemos una propiedad personalizada en un valor nuevo y permitimos que CSS realice la transición según las preferencias de movimiento. La función clave aquí es translateZ(-1px)
. El elemento principal creó un espacio 3D, y este seudo-elemento secundario aprovechó ese espacio colocándose ligeramente hacia atrás en el espacio Z.
Accesibilidad
El video de YouTube muestra una excelente demostración de las interacciones del mouse, el teclado y el lector de pantalla para este componente de configuración. Mencionaré algunos de los detalles aquí.
Opciones de elementos HTML
<form>
<header>
<fieldset>
<picture>
<label>
<input>
Cada uno de ellos contiene sugerencias y consejos para la herramienta de navegación del usuario. Algunos elementos proporcionan sugerencias de interacción, otros conectan la interactividad y otros ayudan a dar forma al árbol de accesibilidad por el que navega un lector de pantalla.
Atributos HTML
Podemos ocultar los elementos que no necesitan los lectores de pantalla, en este caso, el ícono junto al control deslizante:
<picture aria-hidden="true">
En el video anterior, se muestra el flujo del lector de pantalla en Mac OS. Observa cómo el enfoque de entrada se mueve directamente de un control deslizante al siguiente. Esto se debe a que ocultamos el ícono que podría haber sido una parada en el camino hacia el siguiente control deslizante. Sin este atributo, el usuario tendría que detenerse, escuchar y pasar la imagen, que tal vez no pueda ver.
El SVG es un conjunto de cálculos matemáticos. Agreguemos un elemento <title>
para un título gratuito que se muestre cuando se coloque el cursor sobre él y un comentario legible sobre lo que crea el cálculo:
<svg viewBox="0 0 24 24">
<title>A note icon</title>
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
</svg>
Aparte de eso, usamos suficiente código HTML claramente marcado, por lo que el formulario se prueba muy bien con mouse, teclado, controles de videojuegos y lectores de pantalla.
JavaScript
Ya expliqué cómo se administraba el color de relleno del segmento desde JavaScript, así que ahora veamos el JavaScript relacionado con <form>
:
const form = document.querySelector('form');
form.addEventListener('input', event => {
const formData = Object.fromEntries(new FormData(form));
console.table(formData);
})
Cada vez que se interactúa con el formulario y se cambia, la consola registra el formulario como un objeto en una tabla para facilitar la revisión antes de enviarlo a un servidor.
Conclusión
Ahora que sabes cómo lo hice, ¿cómo lo harías tú? Esto genera una arquitectura de componentes divertida. ¿Quién creará la primera versión con ranuras en su framework favorito? 🙂
Diversifiquemos nuestros enfoques y aprendamos todas las formas de crear contenido en la Web. Crea una demostración, envíame un tuit con los vínculos y la agregaré a la sección Remixes de la comunidad a continuación.
Remixes de la comunidad
- @tomayac con su estilo en relación con el área de desplazamiento sobre las etiquetas de las casillas de verificación. Esta versión no tiene espacio entre los elementos al pasar el cursor: demostración y fuente.