Bottom Sheet
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.
Com Header e Footer
Somente Body
- 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.
- 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.
- 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.
- Body: conteúdo rolável do sheet; é a única parte obrigatória da composição.
- Footer (opcional): linha de ações alinhada à direita, reservada para confirmar ou cancelar a seleção em curso.
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.
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".
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.
Usar snap points que deixem ver o contexto da tela de fundo quando o conteúdo não precisa de tudo.
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.
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.
Evitar omitir o Header sem passar aria-label: o sheet fica sem nome acessível para leitores de tela.
Alinhar as ações de confirmação no Footer, à direita.
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-sheetLista 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
| Name | Type | Default | Description |
|---|---|---|---|
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
| Name | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | The content of the sheet header. Accepts any node (title, actions, icons). | |
padding | 'base' | 'base' | The padding around the header content area. |
BottomSheet.Body
| Name | Type | Default | Description |
|---|---|---|---|
children* | React.ReactNode | The scrollable content of the sheet. | |
padding | 'base' | 'base' | The padding around the body content area. |
BottomSheet.Footer
| Name | Type | Default | Description |
|---|---|---|---|
children* | React.ReactNode | The content of the sheet footer. | |
padding | 'base' | '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.