Drawer
Sheet ciente de direção (top, bottom, left, right) construído sobre o vaul com gestos nativos de drag-to-dismiss.
Visão geral
Drawer é um overlay baseado em vaul que desliza a partir de qualquer borda e suporta drag-to-dismiss nativo com momentum. É o primitivo certo para contextos mobile: ações rápidas, formulários simples, action sheets. Em viewports md+ prefira Dialog (decisão focada) ou Sheet (fluxo lateral mais longo), já que a ergonomia de desktop não recompensa bottom sheets.
O padrão recomendado é responsivo: Drawer abaixo de md, Dialog ou Sheet acima. Configure com a prop direction no próprio Drawer.
Preview
'use client';import { Button } from '@gremorie/rx-forms';import { Drawer, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,} from '@gremorie/rx-overlays';export function DrawerPreview() { return ( <Drawer> <DrawerTrigger asChild> <Button variant="outline">Open drawer</Button> </DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle>Move to archive</DrawerTitle> <DrawerDescription> Archived items can be restored within 30 days. </DrawerDescription> </DrawerHeader> <DrawerFooter> <Button>Archive</Button> </DrawerFooter> </DrawerContent> </Drawer> );}Anatomia
Drawer raiz vaul que mantém o estado de abertura + direção
├─ DrawerTrigger elemento que abre o drawer
└─ DrawerContent painel em portal com uma alça de arraste (bottom)
├─ DrawerHeader envolve título + descrição
│ ├─ DrawerTitle título acessível
│ └─ DrawerDescription texto do corpo acessível
└─ DrawerFooter linha de ações inferior
└─ DrawerClose fecha o drawerInstalação
bash npx gremorie@latest add rx-drawer bash pnpm dlx gremorie@latest add rx-drawer bash yarn dlx gremorie@latest add rx-drawer bash bunx --bun gremorie@latest add rx-drawer Uso
import { Button } from "@gremorie/rx-forms";
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@gremorie/rx-overlays";
export function Example() {
return (
<Drawer>
<DrawerTrigger asChild>
<Button variant="outline">Open drawer</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Move to archive</DrawerTitle>
<DrawerDescription>
Archived items can be restored within 30 days.
</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<Button>Archive</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}A edição Angular deste componente hoje é distribuída a partir do source (veja o workbench para o lado a lado); a entrada no registry vem a seguir.
API
<Drawer>
Estende o vaul Drawer.Root. Props notáveis:
| Prop | Type | Default | Description |
|---|---|---|---|
direction | "top" | "bottom" | "left" | "right" | "bottom" | Borda a partir da qual o drawer desliza. |
open | boolean | - | Estado de abertura controlado. |
defaultOpen | boolean | false | Estado de abertura inicial quando não controlado. |
onOpenChange | (open: boolean) => void | - | Disparado quando o estado de abertura muda. |
shouldScaleBackground | boolean | - | Aplica uma escala sutil aos irmãos do drawer (visual iOS). Requer [vaul-drawer-wrapper] em um pai. |
snapPoints | (string | number)[] | - | Posições de repouso (ex.: [0.4, 0.8, 1]). |
activeSnapPoint | string | number | null | - | Snap point controlado. |
onAnimationEnd | (open: boolean) => void | - | Disparado após o arraste/animação assentar. |
modal | boolean | true | Desabilita a interação externa enquanto aberto. |
Todas as demais props do vaul Drawer.Root são encaminhadas.
<DrawerContent>
Envolve o vaul Drawer.Content com o overlay e um Portal. O layout se adapta à direction:
bottom(padrão): encaixa na parte inferior, com topo arredondado, mostra a alça do gripper.top: encaixa no topo, com base arredondada.right/left: painel lateral de altura total,w-3/4limitado asm:max-w-sm.
A alça horizontal do gripper é renderizada automaticamente para direction="bottom".
<DrawerHeader>, <DrawerFooter>, <DrawerTitle>, <DrawerDescription>
Containers de layout com defaults amigáveis ao vaul: o header é centralizado para as direções top/bottom e alinhado à esquerda em md+; o footer se fixa na parte inferior do content.
<DrawerTrigger>, <DrawerClose>, <DrawerOverlay>, <DrawerPortal>
Pass-throughs sobre os primitivos do vaul com atributos data-slot.
Composição
<Drawer>detém o estado de aberto/fechado e o handling de gestos. Definadirectionaqui.<DrawerTrigger asChild>envolve o elemento focável que o abre.<DrawerContent>monta via Portal, desenha o overlay, anima a partir da borda escolhida.- Header guarda o título e a descrição; o footer guarda as ações primárias.
- Dispensa: arraste além do threshold, clique no overlay, pressione
Escou chameDrawerClose.
Variações
Direções
Defina a prop direction no Drawer para deslizar o painel a partir de qualquer borda (top, bottom, left, right). A alça do gripper renderiza automaticamente para bottom.
'use client';import { Button } from '@gremorie/rx-forms';import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger,} from '@gremorie/rx-overlays';const directions = ['top', 'bottom', 'left', 'right'] as const;export function DrawerDirectionsPreview() { return ( <div className="flex flex-wrap gap-2"> {directions.map((direction) => ( <Drawer key={direction} direction={direction}> <DrawerTrigger asChild> <Button variant="outline" className="capitalize"> {direction} </Button> </DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle className="capitalize"> {direction} drawer </DrawerTitle> <DrawerDescription> This drawer slides in from the {direction} edge with native drag-to-dismiss. </DrawerDescription> </DrawerHeader> </DrawerContent> </Drawer> ))} </div> );}Bottom drawer com formulário
O padrão mobile canônico. Formulário rápido, ação primária no footer.
<Drawer>
<DrawerTrigger asChild>
<Button>New note</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>New note</DrawerTitle>
<DrawerDescription>Add a quick note to this project.</DrawerDescription>
</DrawerHeader>
<div className="px-4">
<Textarea placeholder="Type something..." />
</div>
<DrawerFooter>
<Button>Save note</Button>
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>Drawer lateral direito
Troque para um painel lateral para navegação secundária de desktop ou bandejas de filtro.
<Drawer direction="right">
<DrawerTrigger asChild>
<Button variant="outline">Filters</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Filters</DrawerTitle>
</DrawerHeader>
{/* Filter controls */}
</DrawerContent>
</Drawer>Snap points
As posições de repouso deixam o drawer assentar em 40% e então expandir para a altura total. Útil para previews que podem ser expandidos.
const [snap, setSnap] = useState<number | string | null>(0.4);
<Drawer
snapPoints={[0.4, 1]}
activeSnapPoint={snap}
setActiveSnapPoint={setSnap}
>
<DrawerTrigger asChild>
<Button>Preview</Button>
</DrawerTrigger>
<DrawerContent>{/* content scales between 40% and 100% */}</DrawerContent>
</Drawer>;Acessibilidade
- Role:
role="dialog"comaria-modal="true". - Teclado:
Escfecha;Tabcircula o foco dentro do content; o foco fica preso enquanto aberto. - Gestão de foco: o foco entra no drawer na abertura e retorna ao trigger ao fechar.
- Title:
DrawerTitleé exigido para o anúncio em leitor de tela (usesr-onlyse precisar ocultá-lo visualmente). - Dispensa por gesto: o drag-to-dismiss é equivalente ao
Escvia teclado. Usuários de tecnologia assistiva não são penalizados. - Reduced motion: as animações de arraste respeitam
prefers-reduced-motion; o vaul recorre a transições instantâneas.