Como criar um componente de botão de ação flutuante (FAB)

Uma visão geral básica de como criar componentes de FAB adaptáveis, responsivos e acessíveis a cores.

Nesta postagem, quero compartilhar minhas ideias sobre como criar componentes de FAB adaptáveis, responsivos e acessíveis a cores. Teste a demonstração e veja a fonte.

Se preferir vídeo, aqui está uma versão do YouTube desta postagem:

Visão geral

Os FABs são mais comuns em dispositivos móveis do que em computadores, mas prevalem em ambos os cenários. Elas mantêm as ações principais em foco, tornando-as convenientes e onipresentes. Esse estilo de experiência do usuário ficou famoso pela interface do Material Design, e as sugestões de uso e posicionamento podem ser encontradas neste link (link em inglês).

Elementos e estilos

O HTML desses controles envolve um elemento de contêiner e um conjunto de um ou mais botões. O contêiner posiciona os FABs na janela de visualização e gerencia uma lacuna entre os botões. Os botões podem ser mini ou padrão, oferecendo uma boa variedade entre ações principais e secundárias.

Contêiner do FAB

Esse elemento pode ser um <div> normal, mas vamos fazer um favor aos usuários sem visão e marcá-lo com alguns atributos úteis para explicar a finalidade e o conteúdo desse contêiner.

Marcação de FABs

Comece com uma classe .fabs para o CSS se conectar com o estilo e, em seguida, adicione role="group" e aria-label para que não seja apenas um contêiner genérico, ele tenha um nome e tenha uma finalidade.

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

Estilo dos FABs

Para que os FABs sejam convenientes, eles permanecem na janela de visualização o tempo todo. Esse é um ótimo caso de uso para a posição fixed. Nessa posição da janela de visualização, escolhi usar inset-block e inset-inline para que a posição complemente o modo de documento do usuário, como da direita para a esquerda ou da esquerda para a direita. As propriedades personalizadas também são usadas para evitar repetição e garantir uma distância igual das bordas inferior e lateral da janela de visualização:

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

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

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

Em seguida, coloco a tela do contêiner flex e mudo a direção do layout para column-reverse. Isso empilha os filhos uns sobre os outros (coluna) e também inverte a ordem visual deles. Isso faz com que o primeiro elemento focalizável seja o elemento inferior, em vez do topo, que seria para onde o foco normalmente é dirigido pelo documento HTML. A inversão da ordem visual une a experiência de usuários com visão e de teclado, já que o estilo da ação principal como maior do que os minibotões indica para usuários com deficiência visual que essa é uma ação principal, e usuários de teclado focam a ação como o primeiro item na fonte.

Dois botões fab são mostrados com o DevTools sobrepondo o layout de grade. Mostra a lacuna entre eles com um padrão listrado e também mostra a altura e a largura calculadas.

.fabs {
  …

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

A centralização é processada com place-items, e gap adiciona espaço entre todos os botões do FAB colocados no contêiner.

Botões FAB

É hora de definir o estilo de alguns botões para que pareçam estar flutuando por cima de tudo.

FAB padrão

O primeiro botão de estilo é o padrão. Ele servirá como a base para todos os botões do FAB. Mais adiante, vamos criar uma variante que tenha uma aparência alternativa ao modificar o mínimo possível desses estilos base.

Marcação FAB

O elemento <button> é a escolha certa. Vamos começar com isso como a base, porque ele oferece uma ótima experiência do usuário com mouse, toque e teclado. O aspecto mais crucial dessa marcação é ocultar o ícone dos usuários de leitores de tela com aria-hidden="true" e adicionar o texto do rótulo necessário à marcação <button>. Ao adicionar rótulos nesses casos, também gosto de adicionar um title para que os usuários do mouse possam receber informações sobre o que o ícone 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 do FAB

Primeiro, vamos transformar o botão em um botão redondo e preenchido com uma sombra forte, já que estes são os primeiros recursos que definem o botão:

.fab {
  --_size: 2rem;

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

Em seguida, vamos adicionar cor. Usaremos uma estratégia que já usamos nos desafios de GUI antes. Crie um conjunto claramente nomeado de propriedades personalizadas que mantêm estaticamente as cores claras e escuras e, em seguida, uma propriedade personalizada adaptável que será definida como as variáveis claras ou escuras, dependendo da preferência do sistema do usuário por cores:

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

Em seguida, adicione alguns estilos para ajudar os ícones SVG a caber no espaço.

.fab {
  …

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

Por fim, remova o destaque de toque do botão, já que adicionamos nosso próprio feedback visual para interação:

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

Mini FAB

O objetivo desta seção é criar uma variante para o botão FAB. Ao tornar alguns dos FABs menores do que a ação padrão, podemos promover a ação que o usuário realiza com mais frequência.

Marcação de mini FAB

O HTML é igual ao FAB, mas adicionamos uma classe ".mini" para dar ao CSS um gancho para a 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 mini FAB

Graças ao uso de propriedades personalizadas, a única mudança necessária é um ajuste na variável --_size.

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

Uma captura de tela dos dois botões falsos empilhados e o botão de cima é menor que o da parte de baixo.

Acessibilidade

A parte mais importante a ser lembrada para acessibilidade com FABs é o posicionamento dentro do fluxo do teclado da página. Esta demonstração tem apenas os FABs. Não há com que competir em termos de ordem e fluxo do teclado, o que significa que não tem a oportunidade de demonstrar um fluxo de teclado significativo. Em um cenário em que há elementos conflitantes para se concentrar, sugiro pensar profundamente sobre em que parte desse fluxo um usuário deve entrar no fluxo de botões do FAB.

Demonstração de interação com o teclado

Quando o usuário se concentra no contêiner do FAB, já adicionamos role="group" e aria-label="floating action buttons", que informam os usuários de leitores de tela sobre o conteúdo do foco. Estrategicamente, coloquei o FAB padrão primeiro, para que os usuários encontrem a ação principal primeiro. Em seguida, uso flex-direction: column-reverse; para ordenar visualmente o botão principal na parte de baixo, perto dos dedos dos usuários, para facilitar o acesso. Essa é uma boa vitória, porque o botão padrão é visualmente proeminente e também é o primeiro para os usuários de teclado, proporcionando experiências muito semelhantes.

Por fim, não se esqueça de ocultar os ícones dos usuários de leitores de tela e forneça a eles um rótulo para o botão, para não haver mistério. Isso já foi feito no HTML com aria-hidden="true" nos <svg> e aria-label="Some action" nos <button>s.

Animação

Vários tipos de animação podem ser adicionados para aprimorar a experiência do usuário. Como em outros desafios da GUI, vamos configurar algumas propriedades personalizadas para manter a intenção de uma experiência de movimento reduzido e uma experiência de movimento completa. Por padrão, os estilos presumem que o usuário quer movimento reduzido e, usando a consulta de mídia prefers-reduced-motion, troca o valor da transição para movimento total.

Uma estratégia de movimento reduzida com propriedades personalizadas

Três propriedades personalizadas são criadas no seguinte CSS: --_motion-reduced, --_motion-ok e --_transition. As duas primeiras mantêm transições adequadas de acordo com a preferência do usuário, e a última variável --_transition será definida como --_motion-reduced ou --_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);
  }
}

Com o acima implementado, é possível fazer a transição das mudanças em box-shadow, background-color, transform e outline-offset, oferecendo ao usuário um bom feedback sobre a interface de que a interação foi recebida.

Em seguida, adicione um pouco mais de estilo ao estado :active ajustando o translateY um pouco. Isso proporciona ao botão um bom efeito de pressionamento:

.fab {
  …

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

Por fim, faça a transição das mudanças nos ícones SVG nos botões:

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

Conclusão

Agora que você sabe como eu fiz isso, o que você faria ‽ 🙂

Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web.

Crie uma demonstração, envie um tweet para mim e os adicionarei à seção de remixes da comunidade abaixo.

Remixes da comunidade

Ainda não há nada aqui.

Recursos