Una descripción general fundamental de cómo compilar un componente de configuración de controles deslizantes y casillas de verificación.
En esta publicación, quiero compartir mi pensamiento sobre la compilación de un componente de configuración para la Web que sea responsivo, admita varias entradas de dispositivos y funcione en todos los navegadores. Prueba la demostración.
Si prefieres ver un video o quieres obtener una vista previa de la IU o UX de lo que estamos creando, aquí tienes una explicación más breve en YouTube:
Descripción general
He desglosado los aspectos de este componente en las siguientes secciones:
- Diseños
- Color
- Entrada de rango personalizado
- Entrada de casilla de verificación personalizada
- Consideraciones de accesibilidad
- JavaScript
Diseños
Esta es la primera demostración del Desafío de GUI que es completamente de cuadrícula de 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:
foo {
display: grid;
gap: var(--something);
}
Llamo a este diseño “solo para el espacio” porque solo usa la cuadrícula para agregar espacios entre los bloques.
Cinco diseños usan esta estrategia. A continuación, se muestran todos:
El elemento fieldset
, que contiene cada grupo de entrada (.fieldset-item
), usa gap: 1px
para crear los bordes de línea fina entre los elementos. No hay una solución de borde complicada.
.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 de cuadrícula natural
El diseño más complejo terminó siendo el diseño de macro, el sistema de diseño lógico entre <main>
y <form>
.
Cómo centrar el contenido de unión
Flexbox y la cuadrícula proporcionan capacidades a align-items
o align-content
y, cuando se trata de elementos de unión, las alineaciones de diseño 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 sintaxis de alineación place-content: center
para que los elementos secundarios se centren vertical y horizontalmente en diseños de una y dos columnas.
En el video anterior, observa cómo el "contenido" se mantiene centrado, a pesar de que se produjo el encadenamiento.
Repite el ajuste automático de minmax
<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 de diseño sobre esto. Se llama RAM.
Si lo comparas con el de Una, verás que nuestro diseño tiene 3 incorporaciones especiales:
- 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 entrada Intrinsically Responsive CSS Grid with minmax() and min(). Te recomiendo que la leas. La corrección de alineación flex-start
consiste en quitar el efecto de estiramiento predeterminado, de modo que los elementos secundarios de este diseño no tengan que tener alturas iguales, sino que puedan tener alturas naturales e intrínsecas. El video de YouTube incluye un desglose rápido de esta adición de alineación.
max-width: 89vw
vale la pena analizarlo 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 auto-fit
sepa cuántas repeticiones puede ajustar en el espacio. Si bien parece obvio que el espacio es de "ancho completo", según las especificaciones de la cuadrícula de CSS, se debe proporcionar un tamaño definido o un tamaño máximo. Proporcionamos un tamaño máximo.
Entonces, ¿por qué 89vw
? Porque “funcionó” para mi diseño.
Yo y otros usuarios de Chrome estamos investigando por qué un valor más razonable, como 100vw
, no es suficiente y si en realidad 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 se ejecuta muy bien con cuadrícula, CSS @nest y sintaxis de nivel 5 de @media. Este es un ejemplo del conjunto de estilos de diseño completamente <main>
.
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 de viewport disponible, se extiende aumentando el padding. ¡El CSS de 2021 se ve bastante bien!
¿Recuerdas el diseño anterior, "solo para la brecha"? Esta es 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
Un uso controlado del color ayudó a que este diseño se destacara como expresivo pero mínimo. 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);
}
Asigno nombres a mis colores de superficie y texto con números en lugar de nombres como surface-dark
y surface-darker
porque, en una consulta de contenido multimedia, los cambiaré, y los colores claro y oscuro no serán significativos.
Los cambio en una consulta de contenido multimedia 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 obtener una vista rápida del panorama general y la estrategia antes de adentrarnos en los detalles de la sintaxis de colores. Pero, como me estoy adelantando, permite que retroceda un poco.
LCH?
Sin entrar en detalles sobre la teoría del color, LCH es una sintaxis orientada a las personas, que se adapta a la forma en que percibimos el color, no a la forma en que lo medimos con matemáticas (como 255). Esto le brinda una ventaja distintiva, ya que las personas pueden escribirlo con mayor facilidad y otras personas estarán en sintonía con estos ajustes.
Hoy, en esta demostración, enfoquémonos en la sintaxis y los valores que estoy cambiando para crear los colores claro y oscuro. Veamos 1 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 brillo 10%
, cromo 0 y tono 0: un gris incoloro muy oscuro. Luego, en la consulta de medios para el modo claro, la luminosidad se cambia a 90%
con --surface1: lch(90 0 0);
. Y esa es la esencia de la estrategia. Comienza por cambiar la luminosidad entre los 2 temas y mantener las relaciones de contraste que requiere el diseño o lo que puede mantener la accesibilidad.
La ventaja de lch()
aquí es que la ligereza está orientada a las personas, y podemos sentirnos bien con un cambio de %
, que será perceptual y coherentemente esa %
diferente. Por ejemplo, hsl()
no es tan confiable.
Si te interesa, puedes obtener más información sobre los espacios de color y lch()
. ¡Ya está disponible!
Actualmente, el CSS no puede acceder a estos colores en absoluto. Repito: No tenemos acceso a un tercio de los colores de la mayoría de los monitores modernos. Y no son solo cualquier color, sino los colores más vívidos que puede mostrar la pantalla. Nuestros sitios web se desvanecen porque el hardware de los monitores evolucionó más rápido que las especificaciones de CSS y las implementaciones de navegadores.
Lea Verou
Controles de forma adaptables con esquema de colores
Muchos navegadores incluyen controles de tema oscuro, actualmente Safari y Chromium, pero debes especificar en CSS o HTML que tu diseño los usa.
En lo anterior, se muestra el efecto de la propiedad desde el panel de estilos de DevTools. La demostración usa la etiqueta HTML, que, en mi opinión, es 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 las entradas de casilla de verificación oscuras.
CSS accent-color
Hubo actividad reciente en torno a accent-color
en los elementos de formulario, que es un solo estilo CSS que puede cambiar el color de tono que se usa en el elemento de entrada del navegador. Obtén más información aquí en GitHub. Lo incluí en mis estilos para este componente. A medida que los navegadores lo admitan, mis casillas de verificación estarán más en sintonía con el tema con los toques de color rosa y púrpura.
input[type="checkbox"] {
accent-color: var(--brand);
}
Resalta los colores con degradados fijos y enfoque dentro
El color se 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 coloridas de la IU.
Hay muchas capas de comentarios e interacción sobre la IU en el video anterior, que ayudan a darle personalidad a la interacción de la siguiente manera:
- Destaca el contexto.
- 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 entradas
Para proporcionar comentarios cuando se interactúa con un elemento, CSS usa la seudoclase :focus-within
para cambiar la apariencia de varios elementos. Analicemos la .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 el foco dentro:
- Al fondo de
.fieldset-item
se le asigna un color de superficie de contraste más alto. - El
svg
anidado se completa en blanco para obtener un mayor contraste. - El
<picture>
anidadoclip-path
se expande a un círculo completo y el fondo se completa 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 de forma clara te ayude. El resto de los estilos son en su mayoría estilos de restablecimiento para proporcionar una base coherente para compilar las partes difíciles del componente.
Estilos de pista
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 para esto es “revelar” el color de relleno vibrante. Esto se hace con el gradiente de parada dura en la parte superior. El gradiente es transparente hasta el porcentaje de relleno y, luego, utiliza el color de superficie del recorrido sin rellenar. Detrás de esa superficie sin relleno, hay un color de ancho completo que espera a que la transparencia lo revele.
Estilo de relleno de la pista
Mi diseño requiere JavaScript para mantener el estilo de relleno. Solo existen estrategias CSS, pero requieren que el elemento miniatura tenga la misma altura que la pista, 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 es una buena actualización visual. El control deslizante funciona muy bien sin JavaScript. No se requiere el atributo --track-fill
, simplemente no tendrá un estilo de relleno si no está presente. Si JavaScript está disponible, propaga la propiedad personalizada y, al mismo tiempo, observa los cambios del usuario y sincroniza la propiedad personalizada con el valor.
Este es un gran post sobre CSS-Tricks de Ana Tudor, que demuestra una solución solo de 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 son para hacer un círculo agradable.
Una vez más, verás el gradiente de fondo fijo que unifica los colores dinámicos de las miniaturas, las pistas y los elementos SVG asociados.
Separamos los estilos de la interacción para ayudar a aislar la técnica box-shadow
que se usa para el elemento destacado de desplazamiento del mouse:
@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 destacar los elementos visuales animados y fáciles de administrar para los comentarios de los usuarios. Si uso una sombra de cuadro, 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 miniatura. Luego, cambio y transfiero su tamaño de propagación cuando se coloca el cursor sobre él.
Ojalá el efecto de resaltado fuera tan fácil en las casillas de verificación…
Selectores multinavegador
Descubri 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
En 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 estilos transform-style
y position
se preparan para el pseudoelemento que presentaremos más adelante
para aplicar diseño al elemento destacado. De lo contrario, se trata principalmente de aspectos de estilo menores. Me gusta que el cursor sea un puntero, me gustan los desfases de contorno, las casillas de verificación predeterminadas son demasiado pequeñas y, si accent-color
es compatible, lleva estas casillas de verificación al esquema de colores de la marca.
Etiquetas de casillas de verificación
Es importante proporcionar etiquetas para las casillas de verificación por 2 motivos. La primera es representar para qué se usa el valor de la casilla de verificación, a fin de responder "¿para qué?" En segundo lugar, en el caso de 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 la etiqueta, coloca un atributo for
que haga referencia a una casilla de verificación por ID: <label for="text-notifications">
. En la 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 son gratuitos con la conexión, lo que aumenta las formas en que se puede interactuar con tu formulario.
Destacado de casilla de verificación
Quiero mantener la coherencia de mis interfaces, y el elemento del control deslizante tiene un buen elemento destacado 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, y deben ser, cuadradas.
Pude lograr el mismo efecto visual con un seudoelemento y una cantidad desafortunada de CSS:
@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 pseudoelemento de círculo es un trabajo sencillo, pero colocarlo detrás del elemento al que está unido fue más difícil. A continuación, se muestran las imágenes del problema antes y después de solucionarlo:
Definitivamente es una microinteracción, pero para mí es importante mantener la coherencia visual. La técnica de escalamiento de animación es la misma que usamos en otros lugares. Establecemos una propiedad personalizada en un valor nuevo y dejamos que CSS la transfiera según las preferencias de movimiento. La función clave aquí es translateZ(-1px)
. El elemento superior creó un espacio 3D y este seudoelemento secundario se acercó a él al volver a colocarse ligeramente en el espacio z.
Accesibilidad
En el video de YouTube, se 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 para la herramienta de navegación del usuario. Algunos elementos proporcionan sugerencias de interacción, algunos 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 macOS. 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 pudo haber sido una parada en el camino hacia el siguiente control deslizante. Sin este atributo, el usuario tendría que detenerse, escuchar y pasar por alto la imagen que podría no ver.
El SVG es un montón de matemáticas. Agreguemos un elemento <title>
para un título de desplazamiento del mouse libre y un comentario legible por humanos sobre lo que crea la matemática:
<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 HTML claramente marcado, de modo que el formulario se prueba muy bien en mouse, teclado, controles de videojuegos y lectores de pantalla.
JavaScript
Ya analizamos cómo se administraba el color de relleno de la pista desde JavaScript. 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 lo registra como un objeto en una tabla para que se pueda revisar fácilmente antes de enviarlo a un servidor.
Conclusión
Ahora que sabes cómo lo hice, ¿cómo lo harías? Esto genera una arquitectura de componentes divertida. ¿Quién hará la 1ª versión con ranuras en su marco de trabajo favorito? 🙂
Diversifiquemos nuestros enfoques y aprendamos todas las formas de compilar en la Web. Crea una demostración, twittea los vínculos y los agregaré a la sección Remixes de la comunidad a continuación.