KDS
Overlays

Drawer

Bottom sheet mobile-first com gesture support — baseado em vaul.

Overview

Drawer é um bottom sheet baseado em vaul — biblioteca do Emil Kowalski que traz drag-to-dismiss, momentum e o visual familiar de iOS/Android. Mobile-first: ergonomia de polegar, gesture nativo, prende foco como modal mas se sente leve.

A heurística simples: em telas pequenas, Drawer é a melhor casa para confirmações, edições rápidas e seletores. Em telas médias e grandes, troque por Dialog ou Sheet — bottom sheets em desktop forçam o usuário a olhar para baixo, o que é desconfortável.

Preview
Drawer padrão deslizando da base.

Anatomy

<Drawer>
  <DrawerTrigger />
  <DrawerContent>
    {/* handle bar — só visível na direção bottom */}
    <DrawerHeader>
      <DrawerTitle />
      <DrawerDescription />
    </DrawerHeader>
    {/* conteúdo */}
    <DrawerFooter>
      <Button>Confirmar</Button>
      <DrawerClose>Cancelar</DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Subcomponents

ComponenteDescrição
DrawerRaiz, baseada em vaul.
DrawerTriggerElemento que abre o drawer.
DrawerContentContainer com handle bar (em direção bottom).
DrawerHeaderWrapper de título + descrição.
DrawerTitleTítulo obrigatório.
DrawerDescriptionDescrição opcional.
DrawerFooterRodapé com ações.
DrawerCloseWrapper que fecha o drawer.

Usage

import {
  Drawer,
  DrawerTrigger,
  DrawerContent,
  DrawerHeader,
  DrawerTitle,
  DrawerDescription,
  DrawerFooter,
  DrawerClose
} from "@kalvner/kds/overlays/drawer";
import { Button } from "@kalvner/kds/forms/button";

export function ConfirmOrder() {
  return (
    <Drawer>
      <DrawerTrigger asChild>
        <Button>Finalizar</Button>
      </DrawerTrigger>
      <DrawerContent>
        <DrawerHeader>
          <DrawerTitle>Confirmar pedido?</DrawerTitle>
          <DrawerDescription>R$ 258,00 total</DrawerDescription>
        </DrawerHeader>
        <DrawerFooter>
          <Button>Confirmar</Button>
          <DrawerClose asChild>
            <Button variant="outline">Cancelar</Button>
          </DrawerClose>
        </DrawerFooter>
      </DrawerContent>
    </Drawer>
  );
}

Composition — responsive

Padrão útil: usar Drawer em mobile e Dialog em desktop. A escolha vive na lógica de breakpoint da página, não no primitivo.

import { useMediaQuery } from "@/hooks/use-media-query";

export function ResponsiveOverlay({ children }: { children: React.ReactNode }) {
  const isDesktop = useMediaQuery("(min-width: 768px)");
  if (isDesktop) {
    return (
      <Dialog>
        {/* mesmos children, semantic equivalent */}
      </Dialog>
    );
  }
  return (
    <Drawer>
      {/* mesmos children */}
    </Drawer>
  );
}

When to use

  • Confirmações em mobile.
  • Pickers (data, hora, opção) em mobile.
  • Quick actions em apps mobile-web ou PWAs.
  • Bottom-sheet padrão de iOS/Android quando o app foge do feel desktop-default.

When not to use

  • Em desktop puro — use Dialog ou Sheet.
  • Para conteúdo inline e contextual — use Popover.
  • Para fluxos longos com múltiplas seções em mobile — Sheet com side="bottom" em mobile e side="right" em desktop é mais flexível.

Best practices

  • Handle bar comunica drag. A barrinha no topo é affordance visual de "pode arrastar para fechar". Em direção bottom, ela aparece automaticamente.
  • Footer sticky. Ações primárias devem permanecer visíveis quando o conteúdo rola — use mt-auto.
  • Não use em desktop. Mesmo que técnico funcione, viola a convenção do sistema operacional.
  • Drag-to-dismiss respeita o estado. Se houver mudanças não-salvas, intercepte o onOpenChange para confirmar antes de descartar.

Accessibility

ConcernComportamento
Rolesvaul aplica role="dialog" + aria-modal="true".
FocoFocus trap ativo. Escape fecha. Foco volta ao trigger.
KeyboardTab cicla. Esc fecha. Drag é gesture-only.
Screen readerDrawerTitle é o nome; DrawerDescription a descrição.
Reduced motionA animação de slide respeita prefers-reduced-motion no nível do tema.
  • Dialog — equivalente desktop.
  • Sheet — painel lateral para fluxos longos.
  • Popover — overlay inline contextual.

On this page