섹션
컴포넌트
시트
화면 가장자리에서 미끄러지듯 펼쳐지는 패널로, 내비게이션 드로어, 필터 및 편집 양식을 표시하며, 네오브루탈리즘 스타일의 테두리와 그림자 효과가 적용되어 있습니다.
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"시트는 가운데가 아니라 화면 가장자리에 붙는 다이얼로그입니다. 오버레이, 포커스 트랩, 닫기 동작은 같고 위치만 다릅니다. Base UI Dialog 프리미티브를 기반으로 하며, 네오브루탈리즘 공식(두꺼운 테두리, 선명한 그림자, 굵은 서체)에 맞춰 스타일을 입혔습니다.
다음과 같은 경우에 사용합니다.
- 모바일 내비게이션 — 옆에서 미끄러져 들어오는 햄버거 드로어.
- 장바구니·상세 패널 — 페이지를 떠나지 않고 장바구니, 주문 요약, 행 상세를 봅니다.
- 편집 폼 — 테이블을 화면에 둔 채 사이드 패널에서 레코드를 수정합니다.
설치#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/sheet.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/sheet.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/sheet.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/sheet.json
다음 의존성을 설치합니다.
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
다음 코드를 복사해서 프로젝트에 붙여넣습니다.
"use client"
import * as React from "react"
import { XIcon } from "lucide-react"
import { Dialog as SheetPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />
}
function SheetTrigger({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
}
function SheetClose({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
}
function SheetPortal({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
}
function SheetOverlay({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn(
"fixed inset-0 z-50 bg-foreground/20 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className
)}
{...props}
/>
)
}
function SheetContent({
className,
children,
side = "right",
showCloseButton = true,
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left"
showCloseButton?: boolean
}) {
return (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
data-slot="sheet-content"
data-side={side}
className={cn(
"fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-md transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t-2 data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r-2 data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l-2 data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b-2 data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10",
className
)}
{...props}
>
{children}
{showCloseButton && (
<SheetPrimitive.Close data-slot="sheet-close" asChild>
<Button
variant="ghost"
className="absolute top-3 right-3"
size="icon-sm"
>
<XIcon />
<span className="sr-only">Close</span>
</Button>
</SheetPrimitive.Close>
)}
</SheetPrimitive.Content>
</SheetPortal>
)
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-0.5 p-4", className)}
{...props}
/>
)
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
}
function SheetTitle({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn(
"font-head text-base font-medium text-foreground",
className
)}
{...props}
/>
)
}
function SheetDescription({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
}
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}
import 경로를 프로젝트 구성에 맞게 수정합니다.
사용법#
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"<Sheet>
<SheetTrigger>Open</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Are you absolutely sure?</SheetTitle>
<SheetDescription>This action cannot be undone.</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>구성#
Sheet를 만들려면 다음 구성을 사용합니다.
Sheet
├── SheetTrigger
└── SheetContent
├── SheetHeader
│ ├── SheetTitle
│ └── SheetDescription
└── SheetFooter예제#
표시 위치#
SheetContent의 side prop으로 시트가 표시될 화면의 가장자리를 설정합니다. 값은 top, right, bottom, left입니다.
import { Button } from "@/components/ui/button"
import {
Sheet,닫기 버튼 없음#
SheetContent에 showCloseButton={false}를 지정하면 닫기 버튼을 숨길 수 있습니다.
import { Button } from "@/components/ui/button"
import {
Sheet,RTL#
Neobrutalism에서 RTL 지원을 활성화하는 방법은 RTL 설정 가이드를 참고하세요.
"use client"
import {접근성#
시트는 WAI-ARIA Dialog (Modal) 패턴을 따릅니다. 패널은 aria-modal이 있는 role="dialog"로 렌더링되고, 열리면 포커스가 안으로 들어가 가두어지며, SheetTitle / SheetDescription이 aria-labelledby / aria-describedby에 연결됩니다. 필요하면 시각적으로 숨기더라도 항상 SheetTitle을 렌더링하세요.
키보드 상호작용:
| 키 | 동작 |
|---|---|
Space / Enter | 트리거가 포커스된 상태에서 시트 열기 |
Tab / Shift + Tab | 시트 안 포커스 가능 요소 사이 이동(포커스 트랩) |
Esc | 시트를 닫고 트리거로 포커스 복귀 |
API 참조#
자세한 내용은 Base UI 문서를 참고하세요.