Sheet
Painel que desliza de uma borda da tela, ideal para menu mobile, filtros de listagem e edição rápida sem sair da página.
'use client';
import { Button } from '@/components/ui/button';
import { TextField } from '@/components/ui/input';
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet';
export default function SheetDemo() {
return (
<Sheet>
<Button variant="outline">Editar perfil da loja</Button>
<SheetContent>
<SheetHeader>
<SheetTitle>Perfil da loja</SheetTitle>
<SheetDescription>Essas informações aparecem no rodapé do site e no Google.</SheetDescription>
</SheetHeader>
<div className="grid gap-4 px-4">
<TextField label="Nome" defaultValue="Floricultura Jardim Secreto" />
<TextField label="Endereço" defaultValue="Rua das Acácias, 120 — Centro" />
<TextField label="Horário" defaultValue="Seg. a sáb., das 8h às 18h" />
</div>
<SheetFooter>
<Button slot="close">Salvar alterações</Button>
<SheetClose>Cancelar</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
);
}Instalação
npx shadcn@latest add @lenstech/sheetUso
import { Button } from "@/components/ui/button"
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet"<Sheet>
<Button variant="outline">Abrir</Button>
<SheetContent side="right">
<SheetHeader>
<SheetTitle>Perfil da loja</SheetTitle>
<SheetDescription>Dados exibidos no rodapé do site.</SheetDescription>
</SheetHeader>
{/* conteúdo */}
<SheetFooter>
<SheetClose>Fechar</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>Mesma mecânica do Dialog
O Sheet é um modal do React Aria com outra aparência: o primeiro filho de Sheet é o gatilho, o segundo é o
SheetContent, e botões com slot="close" (como SheetClose) fecham o painel. Foco preso, Esc e bloqueio de
rolagem funcionam igual ao Dialog.
Exemplos
Lados
Use side para escolher de qual borda o painel desliza: top, right (padrão), bottom ou left.
'use client';
import { Button } from '@/components/ui/button';
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet';
const sides = [
{ side: 'top', label: 'Topo' },
{ side: 'right', label: 'Direita' },
{ side: 'bottom', label: 'Baixo' },
{ side: 'left', label: 'Esquerda' },
] as const;
export default function SheetSides() {
return (
<div className="grid grid-cols-2 gap-3">
{sides.map(({ side, label }) => (
<Sheet key={side}>
<Button variant="outline">{label}</Button>
<SheetContent side={side}>
<SheetHeader>
<SheetTitle>Painel: {label.toLowerCase()}</SheetTitle>
<SheetDescription>Este painel desliza a partir de side="{side}".</SheetDescription>
</SheetHeader>
<p className="px-4 text-sm">
Use para conteúdo complementar sem tirar o usuário da página atual.
</p>
<SheetFooter>
<SheetClose>Fechar</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
))}
</div>
);
}Menu mobile
Um caso clássico em sites institucionais: o botão de menu abre a navegação pela esquerda. Use a função close para
fechar o painel ao escolher um link.
'use client';
import { Menu as MenuIcon } from 'lucide-react';
import { Button, LinkButton } from '@/components/ui/button';
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
const links = [
{ href: '#inicio', label: 'Início' },
{ href: '#servicos', label: 'Serviços' },
{ href: '#equipe', label: 'Nossa equipe' },
{ href: '#convenios', label: 'Convênios' },
{ href: '#contato', label: 'Contato' },
];
export default function SheetMobileMenu() {
return (
<Sheet>
<Button variant="outline" size="icon" aria-label="Abrir menu">
<MenuIcon />
</Button>
<SheetContent side="left">
{({ close }) => (
<>
<SheetHeader>
<SheetTitle>Clínica Bem-Estar</SheetTitle>
</SheetHeader>
<nav aria-label="Menu principal" className="flex flex-col gap-1 px-2">
{links.map((link) => (
<LinkButton
key={link.href}
href={link.href}
variant="ghost"
className="justify-start"
onPress={close}
>
{link.label}
</LinkButton>
))}
</nav>
<div className="mt-auto p-4">
<LinkButton href="#agendar" className="w-full" onPress={close}>
Agendar consulta
</LinkButton>
</div>
</>
)}
</SheetContent>
</Sheet>
);
}Filtros
Filtros de listagem em um painel lateral mantêm a lista visível ao fundo. As escolhas só são aplicadas ao confirmar.
Exibindo: Pendente
'use client';
import { SlidersHorizontal } from 'lucide-react';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet';
const status = ['Pendente', 'Pago', 'Enviado', 'Cancelado'];
export default function SheetFilters() {
const [selecionados, setSelecionados] = useState<string[]>(['Pendente']);
const [aplicados, setAplicados] = useState<string[]>(['Pendente']);
const toggle = (item: string, marcado: boolean) =>
setSelecionados((atual) => (marcado ? [...atual, item] : atual.filter((s) => s !== item)));
return (
<div className="flex flex-col items-center gap-3">
<Sheet>
<Button variant="outline">
<SlidersHorizontal /> Filtros ({aplicados.length})
</Button>
<SheetContent>
{({ close }) => (
<>
<SheetHeader>
<SheetTitle>Filtrar pedidos</SheetTitle>
<SheetDescription>Escolha quais pedidos aparecem na lista.</SheetDescription>
</SheetHeader>
<fieldset className="grid gap-3 px-4">
<legend className="mb-2 text-sm font-medium">Status</legend>
{status.map((item) => (
<Checkbox
key={item}
isSelected={selecionados.includes(item)}
onChange={(marcado) => toggle(item, marcado)}
>
{item}
</Checkbox>
))}
</fieldset>
<SheetFooter>
<Button
onPress={() => {
setAplicados(selecionados);
close();
}}
>
Aplicar filtros
</Button>
<SheetClose>Cancelar</SheetClose>
</SheetFooter>
</>
)}
</SheetContent>
</Sheet>
<p className="text-sm text-muted-foreground">
Exibindo: {aplicados.length ? aplicados.join(', ') : 'todos os pedidos'}
</p>
</div>
);
}Quando usar
- Use para navegação no mobile, filtros de listagens e edição rápida mantendo o contexto da página.
- Use quando o conteúdo é maior do que cabe num
Popover, mas não merece uma página. - Não use para confirmações — use
Alert Dialog. - Não use para mensagens curtas centradas — use
Dialog.
API
Sheet
Mesmo componente que Dialog (DialogTrigger do React Aria): aceita isOpen,
defaultOpen e onOpenChange.
SheetContent
Aceita todas as props do Dialog do React Aria, mais:
Prop
Type
Demais partes
Prop
Type