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

Una descripción general fundamental de cómo compilar componentes de BAF que se adapten al color, sean responsivos y accesibles.

En esta publicación, quiero compartir mis ideas sobre cómo crear componentes de BAF que se adapten al color y sean responsivos y accesibles. Prueba la demostración y mira la fuente.

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

Descripción general

Los BAF son más comunes en dispositivos móviles que en computadoras de escritorio, pero son frecuentes en ambas situaciones. Mantienen las acciones principales a la vista, por lo que son prácticas y omnipresentes. La IU de Material hizo famoso este estilo de experiencia del usuario, y puedes encontrar aquí tus sugerencias de uso y posición.

Elementos y estilos

El código HTML para estos controles implica un elemento de contenedor y un conjunto de uno o más botones. El contenedor posiciona los BAF 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 del BAF

Este elemento puede ser un <div> normal, pero hagamos un favor a nuestros usuarios ciegos y etiquémoslo 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 vincule con el estilo y, luego, agrega role="group" y aria-label para que no sea solo un contenedor genérico, 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 en el viewport en todo momento. Este es un excelente caso de uso para la posición fixed. Dentro de esta posición de 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 utilizan para evitar repeticiones y garantizar la misma distancia desde 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);
}

Luego, le asigno flex al contenedor flex y cambio la dirección de diseño a column-reverse. Esto apila los elementos secundarios uno sobre 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 sería donde el foco suele ir según el documento HTML. La inversión del orden visual une la experiencia de los usuarios videntes y de teclado, ya que el estilo de la acción principal (más grande que el de los minibotones) indica a los usuarios videntes que es una acción principal, y los usuarios de teclado lo enfocarán como el primer elemento de la fuente.

Se muestran dos botones BAF con las Herramientas para desarrolladores superpuestas en su diseño de cuadrícula. Muestra el espacio 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 centro se controla con place-items, y gap agrega espacio entre los botones del BAF que se colocan en el contenedor.

Botones del BAF

Es hora de diseñar algunos botones para que parezca que flotan sobre todo.

BAF predeterminado

El primer botón para aplicar diseño es el predeterminado. Esto servirá de base para todos los botones del BAF. Más adelante, crearemos una variante que logre una apariencia alternativa y, al mismo tiempo, modifique la menor cantidad posible de estos diseños base.

Lenguaje de marcado del BAF

El elemento <button> es la opción correcta. Comenzaremos con esto como base, ya que ofrece una excelente experiencia del usuario de mouse, panel táctil y teclado. El aspecto más crucial de este lenguaje de marcado es ocultar el ícono de los usuarios de lectores de pantalla con aria-hidden="true" y agregar el texto de la etiqueta necesario al lenguaje de marcado de <button>. En estos casos, cuando agregas etiquetas, también me gusta agregar un title para que los usuarios del mouse puedan obtener información sobre lo que el ícono espera 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 de BAF

Primero, convirtamos el botón en un botón redondo con padding con una sombra fuerte, ya que estas son las primeras funciones que definen el botón:

.fab {
  --_size: 2rem;

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

Ahora, agreguemos color. Usaremos una estrategia que ya usamos en los desafíos de la GUI antes. Crea un conjunto de propiedades personalizadas con un nombre claro que conserve de forma estática los colores claros y oscuros y, luego, una propiedad personalizada adaptable que se establecerá en las variables claro u oscuro según la preferencia del sistema por los colores del usuario:

.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 del botón para presionar, ya que agregamos nuestro propio comentario visual para la interacción:

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

Mini BAF

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

Lenguaje de marcado del mini BAF

El código HTML es el mismo que el BAF, pero agregamos una clase ".mini" para que el CSS tenga un enlace 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 mini BAF

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

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

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

Accesibilidad

La parte más importante que debes recordar para la accesibilidad con BAF es la ubicación dentro del flujo del teclado de la página. Esta demostración solo tiene los BAF y 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 sugerimos que pienses detenidamente en qué parte de ese flujo debería entrar un usuario al flujo del botón del BAF.

Demostración de la interacción con el teclado

Una vez que el usuario se haya enfocado en el contenedor del BAF, ya agregamos role="group" y aria-label="floating action buttons", que informan a los usuarios de lectores de pantalla sobre el contenido en lo que enfocaron. De forma estratégica, coloqué el BAF predeterminado en primer lugar para 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 del usuario para facilitar el acceso. Esta es una buena opción, ya que el botón predeterminado se destaca visualmente y también está primero para los usuarios de 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, de modo que no sea un misterio. Esto se hizo en el HTML con aria-hidden="true" en <svg> y aria-label="Some action" en <button>.

Animación

Se pueden agregar varios tipos de animación para mejorar la experiencia del usuario. Al igual que en otros desafíos de la GUI, configuraremos un par de propiedades personalizadas para conservar el propósito de una experiencia de movimiento reducido y una experiencia de movimiento completa. De forma predeterminada, los diseños supondrán que el usuario desea un movimiento reducido y, luego, con la consulta de medios prefers-reduced-motion, se cambia 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);
  }
}

Una vez que se implementó lo anterior, se puede realizar la transición de los cambios en box-shadow, background-color, transform y outline-offset, lo que proporciona al usuario comentarios positivos en la IU que informa que recibió su interacción.

A continuación, agrega un poco más de estilo al estado :active ajustando un poco translateY. Esto le dará 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 a 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 desarrollar en la Web.

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

Remixes de la comunidad

Aún no hay nada que ver aquí.

Recursos