Bottom Sheet

2.0.0

Apresenta conteúdo ou ações secundárias em uma superfície que desliza a partir da borda inferior da tela, sem perder de vista o contexto da página de fundo. É um padrão exclusivo para dispositivos mobile.

  • Escolher entre várias opções sem sair da tela atual: exibir uma lista de ações ou de valores possíveis (ordenar, filtrar, escolher uma forma de envio) disparada a partir de um botão ou de um item, priorizando a interação tátil no mobile graças ao grabber e aos snap points.
  • Editar ou completar um formulário curto sem perder o contexto de origem: exibir campos de edição rápida que não precisam de uma tela completa, mas sim de mais espaço do que um Popover ou um Dropdown. Ex.: editar uma variante de produto, aplicar um cupom de desconto.
  • Encadear uma sub-etapa sobre um fluxo já aberto: abrir um segundo Bottom Sheet a partir do conteúdo de um já visível, sem fechar o primeiro (suportado de forma nativa: cada sheet se empilha por ordem de montagem e Escape só fecha o que está mais acima). Ex.: escolher um meio de pagamento e, a partir dali, abrir a ajuda para configurá-lo.
  • Exibir um painel persistente que convive com o resto da tela sem bloqueá-la: o Bottom Sheet sempre renderiza uma sobreposição escura de fundo e bloqueia a interação com o resto da página enquanto está aberto; hoje não existe no Nimbus um modo "não bloqueante" para este padrão (por exemplo, um player persistente enquanto se continua navegando pelo resto do app).
  • Precisar de um painel lateral persistente em telas largas: em desktop ou tablet, um painel que ocupa a lateral costuma aproveitar melhor o espaço disponível do que um que se sobrepõe a partir de baixo. Em vez disso, usar Side Modal.
  • Exibir conteúdo tabular extenso com muitas colunas comparáveis: um sheet estreito não é um bom contêiner para tabelas com scroll horizontal. Em vez disso, usar Data Table ou Data List.
  • Exibir um menu de poucas opções ancorado a um elemento pontual, sem necessidade de gestos de arrastar: para esses casos, algo mais leve já é suficiente. Em vez disso, usar Popover.

Formas de envio

Envio padrão, envio expresso e retirada na loja.

Com Header e Footer

Editar produto

Duplicar produto

Excluir produto

Somente Body

  1. Overlay: superfície escura semitransparente que cobre o fundo enquanto o sheet está aberto e bloqueia a interação com a página; tocar nela fecha o sheet, a menos que isso seja desativado com closeOnOutsidePress.
  2. Grabber: pílula arrastável na borda superior do painel; permite redimensionar o sheet entre os snapPoints disponíveis ou descartá-lo com um arraste para baixo, e também é operada pelo teclado.
  3. Header (opcional): cabeçalho do painel; costuma exibir o título da ação ou seleção em curso. Se for omitido, o sheet fica sem nome acessível, a menos que se passe aria-label ou aria-labelledby manualmente.
  4. Body: conteúdo rolável do sheet; é a única parte obrigatória da composição.
  5. Footer (opcional): linha de ações alinhada à direita, reservada para confirmar ou cancelar a seleção em curso.

Conteúdo do sheet

Default (["60%", "90%", "full"]): deixa ver boa parte do contexto de fundo ao abrir, com margem para expandir o sheet se o conteúdo não entrar no primeiro snap. Ex.: uma lista média de opções de envio.

Conteúdo do sheet

Snap único e baixo (ex. ["40%"]): para conteúdo curto e previsível que não precisa se expandir, como uma seleção de 2 ou 3 opções. Ex.: "Ordenar por".

Conteúdo do sheet

defaultSnap apontando para "full": para conteúdo mais longo do que um formulário curto, como a edição completa de uma variante com vários campos. O painel inicia em tela cheia (cantos superiores retos) em vez de em um snap intermediário.

O Bottom Sheet é mobile-first: não tem breakpoints de desktop próprios nem muda de layout entre dispositivos. Costuma ser disparado a partir de um botão, um item de lista ou um ícone de ações, e convive com o restante dos padrões flutuantes do Nimbus (Popover, Modal, Sidebar) sem se fechar se algum deles for aberto a partir do seu próprio conteúdo.

Detalhe do pedido

3 produtos · Envio padrão

Ordenar por

Mais vendidos

Usar snap points que deixem ver o contexto da tela de fundo quando o conteúdo não precisa de tudo.

Ordenar por

Mais vendidos

Evitar abrir sempre em full quando o conteúdo cabe em um snap mais baixo: perde-se a vantagem de não bloquear todo o contexto.

Formas de envio

Envio padrão

Incluir um Header com título quando não se passa aria-label: mantém o sheet acessível e orienta a pessoa sobre o que está vendo.

Envio padrão

Evitar omitir o Header sem passar aria-label: o sheet fica sem nome acessível para leitores de tela.

Cupom de desconto

10% na primeira compra

Alinhar as ações de confirmação no Footer, à direita.

Cupom de desconto

10% na primeira compra

Evitar colocar botões de ação soltos dentro do Body: quebra o padrão visual esperado do Footer.

  • Papel e modalidade automáticos: o sheet aplica role="dialog" e aria-modal="true" por conta própria; não é necessário adicioná-los manualmente.
  • Nome acessível condicionado ao Header: se for usado BottomSheet.Header, o sheet fica vinculado a ele via aria-labelledby de forma automática. Se for omitido, é preciso passar aria-label ou aria-labelledby de forma explícita para que continue acessível.
  • Grabber operável sem gestos táteis: implementado como um "movable splitter" (role="separator"), navegável com ArrowUp/ArrowDown/Home/End e com uma área de toque de 56×56px sobre a pílula visual de 44×4px; movê-lo pelo teclado nunca fecha o sheet.
  • Foco retido e restaurado: ao abrir, o foco fica retido dentro do sheet, priorizando o primeiro elemento interativo do conteúdo em relação ao Grabber; ao fechar, é restaurado no elemento que disparou a abertura.
  • Fechamento consistente com a pilha: se houver vários sheets abertos, Escape fecha apenas o que está mais acima; o clique fora pode ser desativado com closeOnOutsidePress quando o fluxo exigir.
  • Traduzir o grabberLabel: o valor padrão vem em inglês ("Drag to resize or dismiss"); é preciso passar sempre um grabberLabel em português para que o leitor de tela anuncie a instrução no idioma do site.

Instale o componente via terminal.

npm install @nimbus-ds/bottom-sheet

Lista de opções com Header, sem Footer. Cada opção fecha o sheet ao ser selecionada.

import React, { useState } from "react";
import { BottomSheet } from "@nimbus-ds/patterns";
import { Box, Button, List, Text } from "@nimbus-ds/components";
import { mockSortOptions as sortOptions } from "lib/mocks/mock-labels";

const Example: React.FC = () => {
  const [open, setOpen] = useState(false);
  const [selected, setSelected] = useState(sortOptions[0]);

  return (
    <>
      <Button onClick={() => setOpen(true)}>Ordenar por</Button>
      <BottomSheet
        open={open}
        onRemove={() => setOpen(false)}
        grabberLabel="Arrastrar para redimensionar o cerrar"
      >
        <BottomSheet.Header>
          <Text fontWeight="bold" fontSize="highlight">
            Ordenar por
          </Text>
        </BottomSheet.Header>
        <BottomSheet.Body>
          <List>
            {sortOptions.map((option) => (
              <List.Item key={option}>
                <Box
                  as="button"
                  type="button"
                  width="100%"
                  display="flex"
                  justifyContent="space-between"
                  padding="2"
                  aria-pressed={selected === option}
                  onClick={() => {
                    setSelected(option);
                    setOpen(false);
                  }}
                >
                  <Text fontWeight={selected === option ? "bold" : "regular"}>
                    {option}
                  </Text>
                </Box>
              </List.Item>
            ))}
          </List>
        </BottomSheet.Body>
      </BottomSheet>
    </>
  );
};

export default Example;

As propriedades adicionais são passadas para o elemento <BottomSheet>. Consulte a documentação do elemento div para ver a lista de atributos aceitos.

  • Side Modal — alternativa de painel lateral persistente para telas largas.
  • Data Table — alternativa para conteúdo tabular extenso com muitas colunas.
  • Data List — alternativa para listas de dados com muitos itens e busca.
  • Popover — alternativa leve para menus de poucas opções sem gestos de arrastar.

BottomSheet

NameTypeDefaultDescription

open

boolean

'false'

Determines if the bottom sheet is shown or not.

onRemove

object

Callback fired when the component requests to be closed (overlay press, close control, Escape, or a downward dismiss gesture). () => void;

snapPoints

array

'60%,90%,full'

Ordered list of heights the sheet can snap to. Each entry is a viewport-height percentage (e.g. "60%") or the keyword "full".

defaultSnap

number

'0'

Index within `snapPoints` used as the initial snap point.

children

React.ReactNode

Content of the sheet. Compose with BottomSheet.Header, BottomSheet.Body and BottomSheet.Footer.

closeOnOutsidePress

boolean

'true'

Controls whether pressing outside should close the sheet. - boolean: enable/disable dismissal on outside press - function: receive the DOM event and return true to allow closing

This is a responsive property and you can have the options below available for you to use.

'{}'

ignoreAttributeName

string

'data-nimbus-outside-press-ignore'

The attribute name to ignore when checking for outside presses, so portaled Popover/Modal content does not close the sheet.

needRemoveScroll

boolean

'true'

Determines if background scroll is locked while the sheet is open.

grabberLabel

string

'Drag to resize or dismiss'

Accessible name (`aria-label`) for the drag grabber. Override for non-English locales, since the built-in default is English-only.

zIndex

number

Explicit z-index for the sheet layer. Omitted by default: like Nimbus's own Sidebar/Modal/Popover, the sheet relies on DOM mount order rather than a z-index. Its portal mounts into the nearest Nimbus <ThemeProvider>'s own wrapper element (the same container Sidebar/Modal/Popover portal into), falling back to document.body only when no ThemeProvider is present. In both cases, later-mounted siblings paint on top of earlier ones as long as neither sets a z-index, so this keeps the sheet correctly stacked against Popover and other BottomSheet instances. Only set this to force a specific stacking order against something outside that convention.

BottomSheet.Header

NameTypeDefaultDescription

children

React.ReactNode

The content of the sheet header. Accepts any node (title, actions, icons).

padding

'base'
'none'

'base'

The padding around the header content area.

BottomSheet.Body

NameTypeDefaultDescription

children*

React.ReactNode

The scrollable content of the sheet.

padding

'base'
'none'

'base'

The padding around the body content area.

BottomSheet.Footer

NameTypeDefaultDescription

children*

React.ReactNode

The content of the sheet footer.

padding

'base'
'none'

'base'

The padding around the footer content area.

Ajude-nos a melhorar a documentação

Encontrou um problema ou tem uma sugestão? Conte para a gente.