Cómo compilar un componente de configuración

Una descripción general fundamental de cómo crear un componente de configuración con controles deslizantes y casillas de verificación

En esta publicación, quiero compartir las ideas sobre cómo crear un componente de configuración para la Web que sea responsivo, admita varias entradas de dispositivos y funcione en diferentes navegadores. Prueba la demostración.

Demostración

Si prefieres ver un video o quieres obtener una vista previa de la IU o la UX de lo que estamos compilando, aquí tienes una explicación más breve en YouTube:

Descripción general

He desglosado los aspectos de este componente en las siguientes secciones:

  1. Diseños
  2. Color
  3. Entrada de rango personalizado
  4. Entrada personalizada en la casilla de verificación
  5. Consideraciones de accesibilidad
  6. JavaScript

Diseños

Esta es la primera demostración del desafío de la GUI que incluye todos los recursos de CSS Grid. Esta es cada cuadrícula destacada con las herramientas para desarrolladores de Chrome para la cuadrícula:

Contornos coloridos y superposiciones de espaciado de espacios que ayudan a mostrar todos los cuadros que conforman el diseño de la configuración

Solo para el vacío

La disposición más común es la 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. A continuación, se muestran todos:

Diseños de cuadrícula vertical resaltados con contornos y rellenos sin espacios

El elemento fieldset, que contiene cada grupo de entrada (.fieldset-item), usa gap: 1px para crear los bordes finos entre los elementos. No hay una solución de frontera complicada.

Se completó la brecha
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
Truco de borde
.grid {
  display: grid;

  & > .fieldset-item {
    background: var(--bg-surface-2);

    &:not(:last-child) {
      border-bottom: 1px solid var(--bg-surface-1);
    }
  }
}

Envoltorio de cuadrícula natural

El diseño más complejo terminó siendo 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 a align-items o align-content, y cuando se trata de unir elementos, las alineaciones de diseño de content distribuirán el espacio entre los elementos secundarios como grupo.

main {
  display: grid;
  gap: var(--space-xl);
  place-content: center;
}

El elemento principal usa la abreviatura de alineación place-content: center para que los elementos secundarios se centren de forma vertical y horizontal en los diseños de una y dos columnas.

En el video anterior, puedes ver cómo el “contenido” se mantiene en el centro, a pesar de que se realizó la unión.

Repetir ajuste automático mínimo

La <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 column-gap (--space-xxl) para poner ese toque personalizado en el diseño responsivo. Cuando las columnas se apilan, queremos un espacio grande, pero no tan grande como si estuviéramos en una pantalla panorámica.

La propiedad grid-template-columns utiliza 3 funciones de CSS: repeat(), minmax() y min(). Una Kravets tiene una entrada de blog de diseño excelente sobre esto, que se llama RAM.

Nuestro diseño tendrá 3 adiciones especiales, si lo comparas con una:

  • 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 Cuadrícula de CSS intrínsecamente adaptable con minmax() y min(). Te recomendamos leerla. La corrección de alineación de flex-start consiste en 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 intrínsecas y naturales. El video de YouTube tiene un breve desglose de esta adición de alineación.

max-width: 89vw vale un pequeño desglose en esta publicación. Permíteme mostrarte el diseño con y sin el estilo aplicado:

¿Qué sucede? Cuando se especifica max-width, proporciona contexto, tamaño explícito o tamaño definido para el algoritmo de diseño auto-fit a fin de saber cuántas repeticiones puede caber en el espacio. Aunque parezca obvio que el espacio es de "ancho completo", según la especificación de la cuadrícula de CSS, se debe proporcionar un tamaño definido o máximo. Ya incluí un tamaño máximo.

¿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 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 fluye muy bien con grid, CSS @nest y la sintaxis de nivel 5 de @media. Este es un ejemplo del conjunto de estilos de diseño <main> por completo.

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, con relleno moderado de forma predeterminada (como en dispositivos móviles) Sin embargo, a medida que hay más espacio disponible para el viewport, se distribuye aumentando el padding. Los CSS de 2021 se ven bastante bien

¿Recuerdas el diseño anterior, "solo por 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 destaque como expresivo pero mínimo. Lo hago así:

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

Nombraré 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 medios, los invertiré, y claro y oscuro no tendrán sentido.

Hago una consulta de medios de preferencia como la siguiente:

: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 al panorama general y la estrategia antes de profundizar en los detalles de la sintaxis de color. Pero, como me adelanté un poco, permíteme recuperar un poco.

¿LCH?

Sin ahondar en la teoría del color, LCH es una sintaxis orientada a las personas que cubre cómo percibimos el color, no cómo medimos el color con matemáticas (como 255). Esto le da una ventaja distintiva, ya que los seres humanos pueden escribirlo con mayor facilidad y otros seres humanos estarán en sintonía con estos ajustes.

Una captura de pantalla de la página web pod.link/csspodcast, en la que se muestra el episodio Color 2: Perception
Obtén más información sobre el color perceptual (y mucho más) en el Podcast de CSS

Hoy, en esta demostración, nos enfocaremos en la sintaxis y los valores que cambio para hacer 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 10% de luminosidad, 0 croma y 0 matiz: un gris muy oscuro e incoloro. Luego, en la consulta de medios para el modo claro, la luminosidad se cambia a 90% con --surface1: lch(90 0 0);. Esa es la esencia de la estrategia. Para comenzar, cambia la luminosidad entre los 2 temas y mantén las relaciones de contraste que el diseño requiere o lo que puede mantener la accesibilidad.

La ventaja de lch() aquí es que la luminosidad está orientada al ser humano, y podemos sentirnos bien con un cambio de %, que % será diferente de manera perceptual y coherente. hsl(), por ejemplo, no es tan confiable.

Puedes obtener más información sobre los espacios de color y lch() si te interesa. ¡Ya está!

En este momento, CSS no puede acceder a estos colores. Permíteme repetir: No tenemos acceso a un tercio de los colores en la mayoría de los monitores modernos. No se trata de cualquier color, sino de los colores más vívidos que puede mostrar la pantalla. Nuestros sitios web están desordenados debido a que el hardware de supervisión evolucionó más rápido que las especificaciones de CSS y las implementaciones de navegadores.

Lea Verou

Controles de formularios adaptables con esquema de colores

Muchos navegadores lanzan controles de tema oscuro, que actualmente son Safari y Chromium, pero debes especificar en CSS o HTML que los use tu diseño.

Lo anterior muestra el efecto de la propiedad desde el panel Estilos de Herramientas para desarrolladores. La demostración usa la etiqueta HTML, que en mi opinión es, por lo general, una mejor ubicación:

<meta name="color-scheme" content="dark light">

Obtén más información al respecto en este color-schemeartículo de Thomas Steiner. Hay mucho más que obtener que las entradas de casillas de verificación oscuras.

CSS accent-color

Hubo una actividad reciente en torno a accent-color en los elementos del formulario. Se trata de un solo estilo de CSS que puede cambiar el color del tono que se usa en el elemento de entrada de los navegadores. 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 se centrarán más en el tema con los emergentes de color rosa y púrpura.

input[type="checkbox"] {
  accent-color: var(--brand);
}

Captura de pantalla de Chromium en Linux de casillas de verificación rosas

Los colores se destacan con gradientes fijos y enfoque

El color se destaca más cuando se usa con moderación y una de las formas en las que me gusta lograrlo es a través de interacciones coloridas de la IU.

Hay muchas capas de comentarios e interacción de la IU en el video anterior que ayudan a darle personalidad a la interacción al:

  • Destacar el contexto
  • Proporcionar comentarios de la IU sobre “qué tan lleno” está el valor en el rango.
  • Proporcionar comentarios a la IU acerca de que un campo acepta entradas

Para proporcionar comentarios cuando se interactúa con un elemento, CSS usa la seudoclase :focus-within para cambiar el aspecto de varios elementos. Desglosemos .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 está enfocado en lo siguiente:

  1. Al fondo .fieldset-item se le asigna un color de superficie de mayor contraste.
  2. El svg anidado se rellena de color blanco para aumentar el contraste.
  3. El <picture> clip-path anidado se expande a un círculo completo y el fondo se llena con el gradiente fijo brillante.

Período personalizado

Con el siguiente elemento de entrada HTML, te mostraré cómo personalicé su apariencia:

<input type="range">

Debemos personalizar 3 partes de este elemento:

  1. Elemento / contenedor de rango
  2. Realice un seguimiento
  3. Miniatura

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 sea útil etiquetarlas con claridad. El resto de los diseños son, en su mayoría, diseños restablecidos, a fin de proporcionar una base coherente para compilar las partes complicadas 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 es "revelar" el color de relleno vibrante. Esto se hace con el gradiente de detención total en la parte superior. El gradiente es transparente hasta el porcentaje de relleno y, luego, se usa el color de la superficie del recorrido sin completar. Detrás de esa superficie sin rellenar, hay un color de ancho completo que espera a que la transparencia lo revele.

Estilo de relleno del seguimiento

Mi diseño requiere JavaScript para mantener el estilo de relleno. Existen estrategias exclusivas de CSS, pero requieren que el elemento pulgar 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 lo convierte en una buena actualización visual. El control deslizante funciona muy bien sin JavaScript. No se requiere el prop --track-fill; simplemente no tendrá un estilo de relleno si no está presente. Si JavaScript está disponible, propaga la propiedad personalizada mientras observas los cambios de los usuarios y sincroniza la propiedad personalizada con el valor.

Esta es una excelente publicación sobre CSS-Tricks de Ana Tudor, que demuestra una solución única para CSS para el relleno de segmentos. También me pareció muy inspirador este elemento range.

Estilos de pulgar

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 se usan para hacer un círculo agradable. Una vez más, se observa el gradiente fijo de fondo que unifica los colores dinámicos de los pulgares, los seguimientos y los elementos SVG asociados. Separé los estilos de la interacción para aislar la técnica box-shadow que se utiliza para el elemento destacado 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 un elemento visual animado y fácil de administrar para los comentarios de los usuarios. Si uso una sombra de cuadro, puedo evitar la activación del diseño con el efecto. Para ello, creo una sombra que no esté desenfocada y coincida con la forma circular del elemento del pulgar. Luego, cambio y cambio el tamaño de propagación con el cursor.

Si tan solo el efecto para destacar fuera tan fácil en las casillas de verificación...

Selectores multinavegador

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

Con el siguiente elemento de entrada HTML, te mostraré cómo personalicé su apariencia:

<input type="checkbox">

Debemos personalizar 3 partes de este elemento:

  1. Elemento de casilla de verificación
  2. Etiquetas asociadas
  3. Efecto de resaltado

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 seudoelemento que presentaremos más adelante a fin de aplicar diseño al elemento destacado. Por lo demás, es más que nada con un estilo más o menos bien definido. Me gusta que el cursor sea puntero, me gustan los desplazamientos del contorno, las casillas de verificación predeterminadas son demasiado pequeñas y, si se admite accent-color, debes incluirlas en el 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 "activado o desactivado para qué". En segundo lugar, para la UX, los usuarios web están acostumbrados a interactuar con las casillas de verificación a través de sus etiquetas asociadas.

entrada
<input
  type="checkbox"
  id="text-notifications"
  name="text-notifications"
>
etiqueta de recurso
<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 apunte 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.

Casilla de verificación destacada

Quiero mantener la coherencia de mis interfaces, y el elemento del control deslizante tiene una miniatura destacada que me gustaría usar con la casilla de verificación. La miniatura pudo usar box-shadow y su propiedad spread para aumentar y reducir la escala de una sombra. Sin embargo, ese efecto no funciona aquí porque nuestras casillas de verificación son, y deberían ser, cuadradas.

Logré el mismo efecto visual con un pseudoelemento y una cantidad lamentable de CSS complicados:

@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 circular es un trabajo sencillo, pero ubicarlo detrás del elemento al que se adjunta fue más difícil. Esto es antes y después de que lo corregimos:

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 hemos usado en otros lugares. Configuramos una propiedad personalizada con un valor nuevo y dejamos que CSS la transfiera según las preferencias de movimiento. La función principal aquí es translateZ(-1px). El elemento superior creó un espacio 3D y este pseudoelemento secundario aprovechó él colocándose un poco en el espacio z.

Accesibilidad

El video de YouTube hace una gran demostración de las interacciones del mouse, el teclado y el lector de pantalla para este componente de configuración. destacaré algunos de los detalles aquí.

Opciones de elementos HTML

<form>
<header>
<fieldset>
<picture>
<label>
<input>

Cada una contiene sugerencias y sugerencias para la herramienta de navegación del usuario. Algunos elementos proporcionan sugerencias de interacción, otros interactividad de conexión 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 son necesarios para los lectores de pantalla. En este caso, aparece el ícono junto al control deslizante:

<picture aria-hidden="true">

El video de arriba demuestra 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 pudo haber sido una parada en el camino al siguiente control deslizante. Sin este atributo, el usuario tendría que detenerse, escuchar y mover 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 con el cursor del mouse y un comentario legible sobre la creación de 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 suficientemente marcado como para que el formulario pruebe muy bien en el mouse, el teclado, los controles de videojuegos y los lectores de pantalla.

JavaScript

Ya analizamos cómo se administraba el color de relleno del recorrido 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 lo modifica, la consola lo registra como un objeto en una tabla para su fácil revisión antes de enviarlo a un servidor.

Captura de pantalla de los resultados de console.table(), donde los datos del formulario se muestran en una tabla

Conclusión

Ahora que sabes cómo lo hice, ¿cómo lo harías? Esto hace que la arquitectura de componentes sea divertida. ¿Quién creará la 1a versión con ranuras en su framework favorito? 🙂

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 Remixes de la comunidad a continuación.

Remixes de la comunidad

  • @tomayac con su estilo en el área del desplazamiento para las etiquetas de las casillas de verificación. Esta versión no tiene espacio de desplazamiento entre elementos: demo y source.