Chuyển tới nội dung

Pro đang mở bán trước. Cài đặt mở từ tháng 1/2027. Xem Pro

Component

Sheet

Một dialog trượt vào từ một cạnh màn hình và chạy dọc cạnh đó: chi tiết của một hàng, bộ lọc, cài đặt, một form cạnh trang mà nó sửa. Dialog này dùng chung scrim, nền raised và button đóng của Dialog; chỉ cạnh hướng vào nội dung mới có viền và góc trang.

  • npm: 3 trực tiếp, 5 tổng
  • registry: 3 trực tiếp, 4 tổng

Xem trước

Props

  • open
  • defaultOpen
  • side
  • size
  • showCloseButton
  • swipeToDismiss
  • handleLabel
  • title
import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"

<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sửa dự án</Button>
  </SheetTrigger>
  <SheetContent handleLabel="Đóng">
    <SheetHeader>
      <SheetTitle>Sửa dự án</SheetTitle>
      <SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
    </SheetHeader>
    <Field>
      <FieldLabel>Tên</FieldLabel>
      <FieldControl>
        <Input defaultValue="Werewolf" />
      </FieldControl>
    </Field>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="secondary">Hủy</Button>
      </SheetClose>
      <Button>Lưu</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>

Cách dùng

Khi nào dùng

  • Việc làm cạnh trang: chi tiết của một hàng, bộ lọc, một form ngắn.
  • Điều hướng trên màn hình hẹp, từ cạnh đầu, hoặc vài lựa chọn trên điện thoại, từ cạnh dưới.

Khi nào không dùng

  • Xác nhận một hành động xóa dữ liệu. Dùng AlertDialog.
  • Một việc cần tập trung và đứng riêng, như đổi tên một dự án. Dùng Dialog.
  • Một menu các hành động. Dùng DropdownMenu.

Biến thể

side

Đặt trên SheetContent. Cạnh mà sheet trượt vào. right cho chi tiết và form cạnh một danh sách; left cho điều hướng; bottom cho vài lựa chọn ngắn trên điện thoại; top thì hiếm.

  • top

  • rightmặc định

  • bottom

  • left

import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"

<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sửa dự án</Button>
  </SheetTrigger>
  <SheetContent side="top">
    <SheetHeader>
      <SheetTitle>Sửa dự án</SheetTitle>
      <SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
    </SheetHeader>
    <Field>
      <FieldLabel>Tên</FieldLabel>
      <FieldControl>
        <Input defaultValue="Werewolf" />
      </FieldControl>
    </Field>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="secondary">Hủy</Button>
      </SheetClose>
      <Button>Lưu</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>
<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sửa dự án</Button>
  </SheetTrigger>
  <SheetContent>
    <SheetHeader>
      <SheetTitle>Sửa dự án</SheetTitle>
      <SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
    </SheetHeader>
    <Field>
      <FieldLabel>Tên</FieldLabel>
      <FieldControl>
        <Input defaultValue="Werewolf" />
      </FieldControl>
    </Field>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="secondary">Hủy</Button>
      </SheetClose>
      <Button>Lưu</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>
<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sửa dự án</Button>
  </SheetTrigger>
  <SheetContent side="bottom">
    <SheetHeader>
      <SheetTitle>Sửa dự án</SheetTitle>
      <SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
    </SheetHeader>
    <Field>
      <FieldLabel>Tên</FieldLabel>
      <FieldControl>
        <Input defaultValue="Werewolf" />
      </FieldControl>
    </Field>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="secondary">Hủy</Button>
      </SheetClose>
      <Button>Lưu</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>
<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sửa dự án</Button>
  </SheetTrigger>
  <SheetContent side="left">
    <SheetHeader>
      <SheetTitle>Sửa dự án</SheetTitle>
      <SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
    </SheetHeader>
    <Field>
      <FieldLabel>Tên</FieldLabel>
      <FieldControl>
        <Input defaultValue="Werewolf" />
      </FieldControl>
    </Field>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="secondary">Hủy</Button>
      </SheetClose>
      <Button>Lưu</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>

size

Đặt trên SheetContent. Rộng nhất khi ở trái và phải (sm 320px, md 448px, lg 672px), cao nhất khi ở trên và dưới (một phần ba, một nửa, ba phần tư màn hình). full phủ màn hình dọc cạnh đó, không có góc.

  • sm

  • mdmặc định

  • lg

  • full

import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"

<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sửa dự án</Button>
  </SheetTrigger>
  <SheetContent size="sm">
    <SheetHeader>
      <SheetTitle>Sửa dự án</SheetTitle>
      <SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
    </SheetHeader>
    <Field>
      <FieldLabel>Tên</FieldLabel>
      <FieldControl>
        <Input defaultValue="Werewolf" />
      </FieldControl>
    </Field>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="secondary">Hủy</Button>
      </SheetClose>
      <Button>Lưu</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>
<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sửa dự án</Button>
  </SheetTrigger>
  <SheetContent>
    <SheetHeader>
      <SheetTitle>Sửa dự án</SheetTitle>
      <SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
    </SheetHeader>
    <Field>
      <FieldLabel>Tên</FieldLabel>
      <FieldControl>
        <Input defaultValue="Werewolf" />
      </FieldControl>
    </Field>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="secondary">Hủy</Button>
      </SheetClose>
      <Button>Lưu</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>
<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sửa dự án</Button>
  </SheetTrigger>
  <SheetContent size="lg">
    <SheetHeader>
      <SheetTitle>Sửa dự án</SheetTitle>
      <SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
    </SheetHeader>
    <Field>
      <FieldLabel>Tên</FieldLabel>
      <FieldControl>
        <Input defaultValue="Werewolf" />
      </FieldControl>
    </Field>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="secondary">Hủy</Button>
      </SheetClose>
      <Button>Lưu</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>
<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sửa dự án</Button>
  </SheetTrigger>
  <SheetContent size="full">
    <SheetHeader>
      <SheetTitle>Sửa dự án</SheetTitle>
      <SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
    </SheetHeader>
    <Field>
      <FieldLabel>Tên</FieldLabel>
      <FieldControl>
        <Input defaultValue="Werewolf" />
      </FieldControl>
    </Field>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="secondary">Hủy</Button>
      </SheetClose>
      <Button>Lưu</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>

Ví dụ

Sửa chi tiết

import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"

<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sửa dự án</Button>
  </SheetTrigger>
  <SheetContent>
    <SheetHeader>
      <SheetTitle>Sửa dự án</SheetTitle>
      <SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
    </SheetHeader>
    <Field>
      <FieldLabel>Tên</FieldLabel>
      <FieldControl>
        <Input defaultValue="Werewolf" />
      </FieldControl>
    </Field>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="secondary">Hủy</Button>
      </SheetClose>
      <Button>Lưu</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>

Từ cạnh dưới

Vài lựa chọn ngắn trên điện thoại, hành động chính cách một lần chạm.

import { Button } from "@/components/ui/button"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"

<Sheet>
  <SheetTrigger asChild>
    <Button variant="secondary">Sắp xếp</Button>
  </SheetTrigger>
  <SheetContent side="bottom" size="sm">
    <SheetHeader>
      <SheetTitle>Sắp xếp dự án</SheetTitle>
    </SheetHeader>
    <RadioGroup aria-label="Sắp xếp dự án" defaultValue="recent">
      <RadioGroupItem value="recent" label="Chỉnh sửa gần đây" />
      <RadioGroupItem value="name" label="Tên" />
    </RadioGroup>
  </SheetContent>
</Sheet>

Nên và không nên

Nên

  • Dùng cho việc làm cạnh trang: chi tiết của một hàng, bộ lọc, một form ngắn.
  • Cho sheet một title, và đặt hành động chính trong footer.
  • Giữ sheet mở khi người dùng làm việc trong đó; đóng khi hành động xong.

Không nên

  • Đừng dùng cho một xác nhận ngắn; dùng AlertDialog hoặc Dialog.
  • Đừng mở một sheet từ một sheet.
  • Đừng dùng cho một menu các hành động; dùng DropdownMenu.

Props

TênKiểuGiá trịMặc địnhMô tả
openboolean--Đặt trên Sheet. Sheet đang mở hay không, khi bạn kiểm soát nó. Đi cùng onOpenChange. Chỉ trên web.
defaultOpenboolean-falseĐặt trên Sheet. Mở ở lần render đầu, khi bạn không kiểm soát nó. Chỉ trên web.
onOpenChangefunction--Đặt trên Sheet. Được gọi với true hoặc false khi mở hoặc đóng (trigger, button đóng, Escape, scrim). Chỉ trên web.
sideenumtop | right | bottom | leftrightĐặt trên SheetContent. Cạnh mà sheet trượt vào. right cho chi tiết và form cạnh một danh sách; left cho điều hướng; bottom cho vài lựa chọn ngắn trên điện thoại; top thì hiếm.
sizeenumsm | md | lg | fullmdĐặt trên SheetContent. Rộng nhất khi ở trái và phải (sm 320px, md 448px, lg 672px), cao nhất khi ở trên và dưới (một phần ba, một nửa, ba phần tư màn hình). full phủ màn hình dọc cạnh đó, không có góc.
showCloseButtonboolean-trueĐặt trên SheetContent. Button đóng ở góc. Chỉ giấu khi footer có Hủy hoặc Đóng. Chỉ trên web.
swipeToDismissboolean-trueChỉ side bottom (bị bỏ qua ở các side khác): kéo sheet xuống để đóng. Luôn bắt đầu trên handle hoặc header, còn trên body chỉ khi đã cuộn tới đỉnh. Kéo lên thì có lực cản và không bao giờ làm sheet cao thêm. Thả thì đóng khi cú kéo vượt 25 phần trăm chiều cao của sheet hoặc 80px, hoặc tốc độ thả ít nhất 0.5 px/ms hướng xuống; nếu không thì sheet bật lại với duration-medium và ease-standard. Scrim mờ dần theo độ lệch. Lúc đóng kết thúc bằng animate-exit-slide-bottom và gọi onOpenChange(false) đúng một lần, nên focus trở về trigger như Escape. Khi reduced motion, sheet vẫn theo ngón tay, nhưng cú bật lại và lúc thoát thì fade. Escape, button đóng và scrim vẫn hoạt động. Chỉ trong spec: code registry chưa làm phần này.
handleLabelstring-CloseĐặt trên SheetContent, chỉ side bottom. Tên accessible của handle kéo; kích hoạt thì đóng sheet. Mặc định EN `Close`, VI `Đóng`. Chỉ trong spec: code registry chưa làm phần này.

Slot

TênBắt buộcMô tả
triggerkhôngSheetTrigger với asChild bọc một Button.
titlecóSheetTitle, bắt buộc cho a11y.
descriptionkhôngSheetDescription tùy chọn.
contentkhôngPhần body: các field, chi tiết, một danh sách. Phần này cuộn cùng panel.
footerkhôngSheetFooter với các hành động.

Cấu tạo

PhầnMô tả
rootSheet. Giữ trạng thái mở.
overlayScrim phía sau sheet, giống của Dialog.
contentPanel: nền raised, shadow-high, p-xl, gap-lg. Panel phủ hết chiều của cạnh; viền và góc trang chỉ ở cạnh hướng vào nội dung.
headerKhối trên cho title và description, gap-sm.
titleTên của sheet: 16px medium.
descriptionChữ hỗ trợ tùy chọn: 14px secondary.
footerCác hành động ở cuối panel, dù nội dung có ngắn.
closeIcon button ở góc dùng để đóng sheet, giống của Dialog.
iconTabler X trong button đóng, 16px.
hitArea::before trong suốt trên button đóng, không nhỏ hơn size-target-min.

Khả năng truy cập

Role: dialog

PhímHành động
EscapeĐóng sheet và trả focus về trigger.
TabLuân chuyển focus bên trong sheet (focus trap).
  • Một dialog modal: nó bẫy focus, giấu phần còn lại của trang khỏi công nghệ hỗ trợ và đóng khi bấm Escape hoặc nhấn scrim.
  • Luôn có SheetTitle (nhìn thấy hoặc sr-only), để sheet có tên.
  • Button đóng ở góc có nhãn Đóng giấu khỏi mắt và một Tabler X. Với showCloseButton={false}, footer phải có Hủy hoặc Đóng.
  • Sheet chỉ trượt vào khi người dùng cho phép chuyển động; dưới prefers-reduced-motion thì fade vào. Khi đóng, sheet trượt ra lại đúng cạnh đó, nhanh hơn (duration-medium, ease-exit), hoặc fade ra.
  • Đừng mở một sheet từ một sheet.
  • Sheet bottom đệm cạnh dưới bằng env(safe-area-inset-bottom), cần viewport-fit=cover trong meta viewport và lùi về 0px. Vuốt để đóng là một lối thêm; người dùng bàn phím và trình đọc màn hình đóng bằng Escape hoặc button đóng.

Cài đặt

npx werewolf-ui add sheet

Chạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.