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

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

Neste post, quero compartilhar minhas ideias sobre como criar componentes de FAB responsivos, adaptáveis a cores e acessíveis. Teste a demonstração e confira o código-fonte.

Se você preferir o 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 são predominantes em ambos os cenários. Eles mantêm as ações principais em vista, tornando-as convenientes e onnipresentes. Esse estilo de experiência do usuário ficou famoso pela interface do Material Design. As sugestões de uso e posicionamento podem ser encontradas aqui (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 dentro da viewport e gerencia um intervalo entre os botões. Os botões podem ser mini ou padrão, oferecendo uma boa variação entre ações principais e secundárias.

Contêiner do FAB

Esse elemento pode ser um <div> normal, mas vamos ajudar nossos usuários com deficiência visual e marcar 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 que o CSS se conecte para estilizar e, em seguida, adicione role="group" e aria-label para que ele não seja apenas um contêiner genérico, mas nomeado e com uma finalidade.

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

Estilo dos FABs

Para que os botões flutuantes sejam convenientes, eles ficam sempre na janela de visualização. Esse é um ótimo caso de uso para a posição fixed. Nesta posição de viewport, 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ções e garantir distâncias iguais das bordas inferior e lateral da 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);
}

Em seguida, dou a exibição 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 tem o efeito de tornar o primeiro elemento focalizável o elemento de baixo, em vez do de cima, que seria o foco normalmente no documento HTML. A inversão da ordem visual une a experiência para usuários com visão e com teclado, já que o estilo da ação principal é maior que os botões pequenos, indicando aos usuários com visão que se trata de uma ação principal, e os usuários com teclado vão focar nela como o primeiro item na fonte.

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

.fabs {
  

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

O alinhamento central é processado com place-items, e gap adiciona espaço entre os botões de ação flutuante colocados no contêiner.

Botões de ação flutuante

É hora de estilizar alguns botões para que pareçam flutuar sobre tudo.

Botão flutuante padrão

O primeiro botão a receber estilo é o padrão. Ele vai servir como base para todos os botões de ação flutuante. Mais tarde, vamos criar uma variante que alcança uma aparência alternativa, modificando o mínimo possível desses estilos básicos.

Marcação de FAB

O elemento <button> é a escolha certa. Vamos começar com isso como base porque ele oferece uma ótima experiência do usuário com mouse, toque e teclado. O aspecto mais importante dessa marcação é ocultar o ícone dos usuários de leitores de tela com aria-hidden="true" e adicionar o texto de rótulo necessário à marcação <button> em si. 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 FAB

Primeiro, vamos transformar o botão em um botão redondo com preenchimento e uma sombra forte, já que esses 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. Vamos usar uma estratégia que já usamos em desafios de GUI. Crie um conjunto de propriedades personalizadas com nomes claros que mantenham 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 para 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 caberem 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 de ação flutuante. Ao tornar alguns dos botões flutuantes maiores do que a ação padrão, podemos promover a ação que o usuário realiza com mais frequência.

Marcação do Mini FAB

O HTML é o mesmo de um 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 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 de ação flutuante empilhados e o botão de cima é menor que o da parte de baixo.

Acessibilidade

A parte mais importante a ser lembrada para a acessibilidade com os botões flutuantes é a colocação no fluxo do teclado da página. Essa demonstração tem apenas os FABs, não há nada para competir em termos de ordem e fluxo do teclado, o que significa que ela não tem uma oportunidade de demonstrar um fluxo de teclado significativo. Em um cenário em que há elementos concorrentes para o foco, sugiro pensar profundamente sobre em que parte desse fluxo o usuário deve entrar no fluxo do botão FAB.

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

Depois que o usuário focar 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 que eles focaram. Eu coloquei o FAB padrão primeiro, para que os usuários encontrem a ação principal primeiro. Em seguida, utilizo 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. Isso é uma vitória porque o botão padrão é visualmente destacado e também é o primeiro para os usuários de teclado, oferecendo a eles experiências muito semelhantes.

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

Animação

Vários tipos de animação podem ser adicionados para melhorar a experiência do usuário. Assim como em outros desafios de GUI, vamos configurar algumas propriedades personalizadas para manter a intenção de uma experiência de movimento reduzida e uma experiência de movimento total. Por padrão, os estilos vão presumir que o usuário quer um movimento reduzido e, em seguida, usar a consulta de mídia prefers-reduced-motion para trocar o valor da transição para movimento total.

Uma estratégia de movimento reduzido com propriedades personalizadas

Três propriedades personalizadas são criadas no CSS a seguir: --_motion-reduced, --_motion-ok e --_transition. As duas primeiras contê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 em vigor, as mudanças em box-shadow, background-color, transform e outline-offset podem ser transferidas, dando ao usuário um bom feedback de interface informando que a interação foi recebida.

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

.fab {
  

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

Por fim, faça a transição de todas as alterações 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, como você faria? 🙂

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

Crie uma demonstração, envie links para mim e vou adicionar à seção de remixes da comunidade abaixo.

Remixes da comunidade

Ainda não há nada aqui.

Recursos