Cómo compilar un componente de botón de acción flotante (BAF)

Una descripción general fundamental sobre cómo compilar componentes de BAF responsivos, accesibles y adaptables al color.

En esta publicación, quiero compartir mis ideas sobre cómo compilar componentes de BAF responsivos, accesibles y adaptables al color. Prueba la demo y consulta la fuente.

Si prefieres ver un video, aquí tienes una versión de esta publicación en YouTube:

Descripción general

Los FAB son más comunes en dispositivos móviles que en computadoras, pero son frecuentes en ambos casos. Mantienen las acciones principales a la vista, lo que las hace convenientes y omnipresentes. Material UI popularizó este estilo de experiencia del usuario, y sus sugerencias de uso y ubicación se pueden encontrar aquí.

Elementos y estilos

El código HTML de estos controles incluye un elemento contenedor y un conjunto de uno o más botones. El contenedor posiciona los FAB dentro del viewport y administra un espacio entre los botones. Los botones pueden ser mini o predeterminados, lo que ofrece una buena variedad entre las acciones principales y secundarias.

Contenedor de BAF

Este elemento puede ser un <div> normal, pero hagamos un favor a nuestros usuarios con discapacidad visual y etiquétalo con algunos atributos útiles para explicar el propósito y el contenido de este contenedor.

Lenguaje de marcado de los BAF

Comienza con una clase .fabs para que CSS se conecte y aplique el estilo. Luego, agrega role="group" y aria-label para que no sea solo un contenedor genérico, sino que tenga un nombre y un propósito.

<div class="fabs" role="group" aria-label="Floating action buttons">
  <!-- buttons will go here -->
</div>

Estilo de los BAF

Para que los BAF sean convenientes, permanecen dentro del viewport en todo momento. Este es un excelente caso de uso para la posición fixed. Dentro de esta posición del viewport, elegí usar inset-block y inset-inline para que la posición complemente el modo de documento del usuario, como de derecha a izquierda o de izquierda a derecha. Las propiedades personalizadas también se usan para evitar la repetición y garantizar una distancia igual de los bordes inferior y lateral del viewport:

.fabs {
  --_viewport-margin: 2.5vmin;

  position: fixed;
  z-index: var(--layer-1);

  inset-block: auto var(--_viewport-margin);
  inset-inline: auto var(--_viewport-margin);
}

A continuación, le doy a la pantalla del contenedor flex y cambio su dirección de diseño a column-reverse. Esto apila los elementos secundarios uno encima del otro (columna) y también invierte su orden visual. Esto tiene el efecto de hacer que el primer elemento enfocable sea el elemento inferior en lugar del superior, que es donde se enfoca normalmente según el documento HTML. Invertir el orden visual une la experiencia de los usuarios con visión y los usuarios del teclado, ya que el diseño de la acción principal es más grande que los botones en miniatura, lo que indica a los usuarios con visión que es una acción principal, y los usuarios del teclado la enfocarán como el primer elemento de la fuente.

Se muestran dos botones de FAB con DevTools superpuestos en su diseño de cuadrícula. Muestra la brecha entre ellos con un patrón a rayas y también muestra su altura y ancho calculados.

.fabs {
  

  display: flex;
  flex-direction: column-reverse;
  place-items: center;
  gap: var(--_viewport-margin);
}

El centrado se controla con place-items, y gap agrega espacio entre los botones de BAF que se colocan en el contenedor.

Botones de BAF

Es hora de aplicar diseño a algunos botones para que parezcan estar flotando sobre todo.

BAF predeterminado

El primer botón al que se le aplica diseño es el predeterminado. Esto servirá como base para todos los botones de FAB. Más adelante, crearemos una variante que logre una apariencia alternativa y modifique lo menos posible estos diseños base.

Lenguaje de marcado del BAF

El elemento <button> es la elección correcta. Empezaremos con esto como base, ya que ofrece una excelente experiencia del usuario del mouse, el modo táctil y el teclado. El aspecto más crucial de este marcado es ocultar el ícono a los usuarios de lectores de pantalla con aria-hidden="true" y agregar el texto de la etiqueta necesario al marcado <button>. Cuando agrego etiquetas en estos casos, también me gusta agregar un title para que los usuarios del mouse puedan obtener información sobre lo que el ícono intenta comunicar.

<button data-icon="plus" class="fab" title="Add new action" aria-label="Add new action">
  <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">...</svg>
</button>

Estilo del BAF

Primero, convertiremos el botón en uno redondo acolchado con una sombra fuerte, ya que estas son las primeras características definitorias del botón:

.fab {
  --_size: 2rem;

  padding: calc(var(--_size) / 2);
  border-radius: var(--radius-round);
  aspect-ratio: 1;
  box-shadow: var(--shadow-4);
}

A continuación, agreguemos color. Usaremos una estrategia que ya usamos en desafíos de GUI. Crea un conjunto de propiedades personalizadas con un nombre claro que contenga de forma estática los colores claros y oscuros, y, luego, una propiedad personalizada adaptable que se establecerá en las variables claras o oscuras según la preferencia del sistema del usuario para los colores:

.fab {
  

  /* light button and button hover */
  --_light-bg: var(--pink-6);
  --_light-bg-hover: var(--pink-7);

  /* dark button and button hover */
  --_dark-bg: var(--pink-4);
  --_dark-bg-hover: var(--pink-3);

  /* adaptive variables set to light by default */
  --_bg: var(--_light-bg);

  /* static icon colors set to the adaptive foreground variable */
  --_light-fg: white;
  --_dark-fg: black;
  --_fg: var(--_light-fg);

  /* use the adaptive properties on some styles */
  background: var(--_bg);
  color: var(--_fg);

  &:is(:active, :hover, :focus-visible) {
    --_bg: var(--_light-bg-hover);

    @media (prefers-color-scheme: dark) {
      --_bg: var(--_dark-bg-hover);
    }
  }

  /* if users prefers dark, set adaptive props to dark */
  @media (prefers-color-scheme: dark) {
    --_bg: var(--_dark-bg);
    --_fg: var(--_dark-fg);
  }
}

A continuación, agrega algunos estilos para ayudar a que los íconos SVG se ajusten al espacio.

.fab {
  

  & > svg {
    inline-size: var(--_size);
    block-size: var(--_size);
    stroke-width: 3px;
  }
}

Por último, quita el elemento destacado de presión del botón, ya que agregamos nuestro propio comentario visual para la interacción:

.fab {
  -webkit-tap-highlight-color: transparent;
}

BAF pequeño

El objetivo de esta sección es crear una variante para el botón de BAF. Si hacemos que algunos de los FAB sean más pequeños que la acción predeterminada, podemos promocionar la acción que el usuario realiza con más frecuencia.

Lenguaje de marcado de FAB pequeño

El HTML es lo mismo que un BAF, pero agregamos una clase ".mini" para vincular a CSS con la variante.

<button data-icon="heart" class="fab mini" title="Like action" aria-label="Like action">
  <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">...</svg>
</button>
Estilo de BAF pequeño

Gracias al uso de las propiedades personalizadas, el único cambio necesario es un ajuste en la variable --_size.

.fab.mini {
  --_size: 1.25rem;
}

Una captura de pantalla de los dos botones BAF apilados y el botón superior es más pequeño que el de abajo.

Accesibilidad

La parte más importante que se debe recordar para la accesibilidad con los FAB es la ubicación dentro del flujo del teclado de la página. Esta demostración solo tiene los FAB, no hay nada con lo que competir en términos de orden y flujo del teclado, lo que significa que no tiene la oportunidad de demostrar un flujo de teclado significativo. En una situación en la que hay elementos que compiten por el enfoque, te sugiero que pienses detenidamente en qué parte de ese flujo debería un usuario entrar en el flujo del botón del BAF.

Demostración de interacción con el teclado

Una vez que el usuario enfocó el contenedor de FAB, ya agregamos role="group" y aria-label="floating action buttons", que informan a los usuarios del lector de pantalla sobre el contenido en el que se enfocaron. De manera estratégica, coloqué el BAF predeterminado primero, de modo que los usuarios encuentren primero la acción principal. Luego, uso flex-direction: column-reverse; para ordenar visualmente el botón principal en la parte inferior, cerca de los dedos de los usuarios para facilitar el acceso. Esta es una buena ventaja, ya que el botón predeterminado es visualmente prominente y también es el primero para los usuarios del teclado, lo que les brinda experiencias muy similares.

Por último, no olvides ocultar los íconos a los usuarios de lectores de pantalla y asegúrate de proporcionarles una etiqueta para el botón para que no sea un misterio. Esto ya se hizo en HTML con aria-hidden="true" en <svg> y aria-label="Some action" en <button>.

Animación

Se pueden agregar varios tipos de animaciones para mejorar la experiencia del usuario. Al igual que en otros desafíos de GUI, configuraremos un par de propiedades personalizadas para contener la intención de una experiencia de movimiento reducido y una experiencia de movimiento completo. De forma predeterminada, los estilos asumirán que el usuario desea reducir el movimiento y, luego, con la consulta de medios prefers-reduced-motion, intercambiarán el valor de transición a movimiento completo.

Una estrategia de movimiento reducido con propiedades personalizadas

Se crean tres propiedades personalizadas en el siguiente CSS: --_motion-reduced, --_motion-ok y --_transition. Las dos primeras contienen transiciones adecuadas según la preferencia del usuario, y la última variable --_transition se establecerá en --_motion-reduced o --_motion-ok, respectivamente.

.fab {
  /* box-shadow and background-color can safely be transitioned for reduced motion users */
  --_motion-reduced:
    box-shadow .2s var(--ease-3),
    background-color .3s var(--ease-3);

  /* add transform and outline-offset for users ok with motion */
  --_motion-ok:
    var(--_motion-reduced),
    transform .2s var(--ease-3),
    outline-offset 145ms var(--ease-2);

  /* default the transition styles to reduced motion */
  --_transition: var(--_motion-reduced);

  /* set the transition to our adaptive transition custom property*/
  transition: var(--_transition);

  /* if motion is ok, update the adaptive prop to the respective transition prop */
  @media (prefers-reduced-motion: no-preference) {
    --_transition: var(--_motion-ok);
  }
}

Con lo anterior implementado, se puede realizar la transición de los cambios en box-shadow, background-color, transform y outline-offset, lo que le brinda al usuario buenos comentarios de la IU de que recibió su interacción.

A continuación, agrega un poco más de estilo al estado :active ajustando un poco translateY. Esto le da al botón un buen efecto de presión:

.fab {
  

  &:active {
    @media (prefers-reduced-motion: no-preference) {
      transform: translateY(2%);
    }
  }
}

Por último, realiza la transición de cualquier cambio en los íconos SVG en los botones:

.fab {
  

  &[data-icon="plus"]:hover > svg {
    transform: rotateZ(.25turn);
  }

  & > svg {
    @media (prefers-reduced-motion: no-preference) {
      will-change: transform;
      transition: transform .5s var(--ease-squish-3);
    }
  }
}

Conclusión

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

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

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

Remixes de la comunidad

Aún no hay nada que ver.

Recursos