Saltar al contenido principal
NachUI

Sheet

Un panel fijado a un borde de la pantalla, para filtros, detalles y formularios que no deberían sacarte de la página.

1'use client';
2
3import { Button } from '@/components/ui/button';
4import { Checkbox } from '@/components/ui/checkbox';
5import { Label } from '@/components/ui/label';
6import { Sheet } from '@/components/ui/sheet';
7
8const FILTERS = [
9 { id: 'open', label: 'Open pull requests', checked: true },
10 { id: 'mine', label: 'Assigned to me', checked: true },
11 { id: 'draft', label: 'Include drafts', checked: false },
12 { id: 'ci', label: 'Failing checks only', checked: false },
13];
14
15export function Default() {
16 return (
17 <Sheet>
18 <Sheet.Trigger asChild>
19 <Button variant="outline">Filters</Button>
20 </Sheet.Trigger>
21 <Sheet.Content>
22 <Sheet.Header>
23 <Sheet.Title>Filters</Sheet.Title>
24 <Sheet.Description>Narrow the list without leaving the page.</Sheet.Description>
25 </Sheet.Header>
26 <Sheet.Body className="flex flex-col gap-4">
27 {FILTERS.map((filter) => (
28 <div key={filter.id} className="flex items-center gap-3">
29 <Checkbox id={filter.id} defaultChecked={filter.checked} />
30 <Label htmlFor={filter.id} className="text-sm">
31 {filter.label}
32 </Label>
33 </div>
34 ))}
35 </Sheet.Body>
36 <Sheet.Footer>
37 <Sheet.Close asChild>
38 <Button variant="ghost" size="sm">
39 Reset
40 </Button>
41 </Sheet.Close>
42 <Sheet.Close asChild>
43 <Button size="sm">Apply</Button>
44 </Sheet.Close>
45 </Sheet.Footer>
46 </Sheet.Content>
47 </Sheet>
48 );
49}

Instalación

pnpm dlx nachui add sheet

Anatomía

1import { Sheet } from '@/components/ui/sheet';
1<Sheet>
2 <Sheet.Trigger asChild>
3 <Button variant="outline">Filtros</Button>
4 </Sheet.Trigger>
5 <Sheet.Content side="right" size="md">
6 <Sheet.Header>
7 <Sheet.Title>Filtros</Sheet.Title>
8 <Sheet.Description>Acotá la lista sin salir de la página.</Sheet.Description>
9 </Sheet.Header>
10 <Sheet.Body>...</Sheet.Body>
11 <Sheet.Footer>
12 <Sheet.Close asChild>
13 <Button>Aplicar</Button>
14 </Sheet.Close>
15 </Sheet.Footer>
16 </Sheet.Content>
17</Sheet>
Un sheet va pegado al borde desde el que abre y lo cubre de punta a punta. Atrapa el foco, cierra con Escape y con el fondo, bloquea el scroll de la página y devuelve el foco al trigger cuando se va.

Sheet o Drawer

Se parecen y son familia, pero responden preguntas distintas. Drawer es el táctil: una isla flotante con una manija que arrastrás para cerrar, cómodo en un teléfono. Sheet es el de escritorio: un panel lateral fijo sin arrastre, con tamaños en escalones, pensado para un riel de filtros, un formulario de edición o una vista de detalle. Si el contenido es algo que se completa, usá Sheet. Si es algo que se descarta con un gesto, usá Drawer.

Composición

Usa la siguiente composición para construir un Sheet:
Sheet
├── Sheet.Trigger
└── Sheet.Content
├── Sheet.Header
│ ├── Sheet.Title
│ └── Sheet.Description
├── Sheet.Body
└── Sheet.Footer
└── Sheet.Close
Header, Body y Footer son layout: el cuerpo scrollea solo y el pie se queda abajo, así los formularios largos mantienen las acciones a mano.

Lados

1'use client';
2
3import { Button } from '@/components/ui/button';
4import { Sheet, type SheetSide } from '@/components/ui/sheet';
5
6const SIDES: SheetSide[] = ['left', 'right', 'top', 'bottom'];
7
8export function Sides() {
9 return (
10 <div className="flex flex-wrap gap-2">
11 {SIDES.map((side) => (
12 <Sheet key={side}>
13 <Sheet.Trigger asChild>
14 <Button variant="outline" size="sm" className="capitalize">
15 {side}
16 </Button>
17 </Sheet.Trigger>
18 <Sheet.Content side={side}>
19 <Sheet.Header>
20 <Sheet.Title className="capitalize">{side}</Sheet.Title>
21 <Sheet.Description>A sheet anchored to the {side} edge.</Sheet.Description>
22 </Sheet.Header>
23 <Sheet.Body>
24 <p className="text-muted-foreground text-sm">
25 Press Escape, click the backdrop or use the close button to dismiss it.
26 </p>
27 </Sheet.Body>
28 </Sheet.Content>
29 </Sheet>
30 ))}
31 </div>
32 );
33}

Tamaños

size es un ancho a izquierda y derecha, y una altura máxima arriba y abajo. full cubre la pantalla.
1'use client';
2
3import { Button } from '@/components/ui/button';
4import { Sheet, type SheetSize } from '@/components/ui/sheet';
5
6const SIZES: SheetSize[] = ['sm', 'md', 'lg', 'xl', 'full'];
7
8export function Sizes() {
9 return (
10 <div className="flex flex-wrap gap-2">
11 {SIZES.map((size) => (
12 <Sheet key={size}>
13 <Sheet.Trigger asChild>
14 <Button variant="outline" size="sm">
15 {size}
16 </Button>
17 </Sheet.Trigger>
18 <Sheet.Content size={size}>
19 <Sheet.Header>
20 <Sheet.Title>Size {size}</Sheet.Title>
21 <Sheet.Description>Width on the sides, height on top and bottom.</Sheet.Description>
22 </Sheet.Header>
23 </Sheet.Content>
24 </Sheet>
25 ))}
26 </div>
27 );
28}

Referencia de API

Sheet

PropTypeDefaultDescription
defaultOpenbooleanfalseEstado abierto cuando no se controla
openboolean-Estado abierto controlado
onOpenChange(open: boolean) => void-Se llama al cambiar el estado

Sheet.Trigger

PropTypeDefaultDescription
asChildbooleanfalseRenderiza el hijo como trigger en vez de un botón
classNamestring-Clases CSS adicionales

Sheet.Content

PropTypeDefaultDescription
side'top' | 'bottom' | 'left' | 'right''right'Borde al que se fija
size'sm' | 'md' | 'lg' | 'xl' | 'full''md'Ancho a los lados, alto arriba y abajo
showClosebooleantrueMuestra el botón de cerrar de la esquina
closeLabelstring'Close'Nombre accesible de ese botón
classNamestring-Clases CSS adicionales

Sheet.Close

PropTypeDefaultDescription
asChildbooleanfalseRenderiza el hijo como cerrador en vez de un botón
classNamestring-Clases CSS adicionales

Sheet.Header / Sheet.Body / Sheet.Footer / Sheet.Title / Sheet.Description

PropTypeDefaultDescription
classNamestring-Clases CSS adicionales
¿Encontraste algo que mejorar?

¿Notaste un error, tipografía o detalle faltante en esta página? Ayúdanos a mejorar la documentación abriendo un issue en GitHub.

Crear un Issue