Sheet
Ein modales Panel, das von jeder Kante hereingleitet, mit festem Kopf, scrollendem Inhalt und haftender Fußzeile.
Eine von 9 Components in Overlays
Details
Was Sheet mitbringt, aus Registry und Quellcode gelesen.
- Kategorie
- Overlays
- Bereich
- Elements
- Variants
- 2
- Version
- 1.0.0 · seit 1.0.0
- Datei
- components/ui/sheet.tsx
- npm-Pakete
- Keine
- Sprachen
- Englisch und Deutsch, 1 eigener Text
- Studio
- Im Code anpassen
- Mit KI bauen
- Per MCP in Claude, Cursor, Copilot und Codex
Props
Die Schnittstelle SheetProps aus components/ui/sheet.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch. Nimmt außerdem alle Props von Omit<SheetPanelProps, 'onClose' | 'titleId' | 'descriptionId' | 'footer'> an.
triggeroptionalReact.ReactElementopenoptionalbooleandefaultOpenoptionalbooleanonOpenChangeoptional(open: boolean) => voidfooteroptionalReact.ReactNode | ((close: () => void) => React.ReactNode)
Eingebaute Texte
Diese Texte schreibt Sheet selbst. Die Sprache folgt dem KitProvider, über die Prop labels ersetzt du einzelne.
Alle 1 Texte anzeigen
| Schlüssel | Englisch | Deutsch |
|---|---|---|
| close | Englisch Close | Deutsch Schließen |
Barrierefreiheit
Aus dem Quellcode der Component abgeleitet.
- Baut auf dem nativen dialog-Element auf. Der Browser hält den Fokus im Fenster und schließt es mit Escape.
- Setzt aria-describedby, aria-label und aria-labelledby, damit Screenreader Zweck und Zustand ansagen.
- Kommt ohne Client-State aus. Der Server schreibt sie vollständig, sie ist auch ohne JavaScript lesbar.
Bewegung
- Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig. 1 Stellen im Code schalten Bewegung dann ab.
- Ohne Animationen, sie steht sofort ruhig da.
Quellcode
Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.
Datei anzeigen, 109 Zeilen
'use client';
import { useId } from 'react';
import { cn, useTexts } from '@/lib/kit';
import { Icon } from '@/components/ui/icon';
import { DialogMotionStyle, scrim, useDialogState, useModalMotion, withTrigger } from '@/components/ui/dialog';
const TEXTS = { en: { close: 'Close' }, de: { close: 'Schließen' } };
export type SheetSide = 'left' | 'right' | 'top' | 'bottom';
/* Where the sheet rests. It slides in and out from its edge (see useModalMotion), so no translate here. */
const PLACE: Record<SheetSide, string> = {
right: 'ms-auto me-0 h-dvh max-h-dvh w-[min(26rem,calc(100vw-2.5rem))]',
left: 'ms-0 me-auto h-dvh max-h-dvh w-[min(22rem,calc(100vw-2.5rem))]',
top: 'mt-0 mb-auto w-full max-w-none',
bottom: 'mb-0 mt-auto max-h-[88dvh] w-full max-w-none',
};
const SURFACE: Record<SheetSide, string> = {
right: 'border-s',
left: 'border-e',
top: 'border-b rounded-b-2xl',
bottom: 'border-t rounded-t-2xl',
};
export interface SheetPanelProps {
side?: SheetSide;
title: React.ReactNode;
description?: React.ReactNode;
footer?: React.ReactNode;
onClose?: () => void;
titleId?: string;
descriptionId?: string;
labels?: Partial<(typeof TEXTS)['en']>;
className?: string;
children?: React.ReactNode;
}
/** The surface of a sheet: header with close, a scrolling body and a sticky footer. */
export function SheetPanel({ side = 'right', title, description, footer, onClose, titleId, descriptionId, labels, className, children }: SheetPanelProps) {
const t = useTexts(TEXTS, labels);
const vertical = side === 'top' || side === 'bottom';
return (
<div className={cn('flex size-full max-h-[inherit] flex-col border-border bg-popover text-popover-foreground shadow-[var(--shadow-lg)]', SURFACE[side], className)}>
{side === 'bottom' && <span aria-hidden className="mx-auto mt-2.5 h-1 w-10 shrink-0 rounded-full bg-border-strong" />}
<div className={cn('kitfx-dialog-part flex shrink-0 items-start gap-4 px-6 pb-4', side === 'bottom' ? 'pt-4' : 'pt-6', vertical && 'mx-auto w-full max-w-[44rem]')}>
<div className="min-w-0 flex-1">
<h2 id={titleId} className="text-[17px] font-semibold leading-snug tracking-[-0.015em] text-foreground">
{title}
</h2>
{description && (
<p id={descriptionId} className="mt-1 text-pretty text-sm leading-relaxed text-muted-foreground">
{description}
</p>
)}
</div>
<button type="button" onClick={onClose} aria-label={t.close} className="group/close -me-2 -mt-1 grid size-9 shrink-0 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
<Icon name="x" size={17} className="transition-transform duration-300 ease-[var(--ease-kit)] group-hover/close:rotate-90 motion-reduce:transition-none" />
</button>
</div>
{children && <div className={cn('kitfx-dialog-part kit-scrollbar min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 pb-6', vertical && 'mx-auto w-full max-w-[44rem]')}>{children}</div>}
{footer && (
<div className="kitfx-dialog-part shrink-0 border-t border-border bg-surface px-6 py-4">
<div className={cn('flex flex-col-reverse gap-2.5 sm:flex-row sm:justify-end [&>*]:max-sm:w-full', vertical && 'mx-auto max-w-[44rem]')}>{footer}</div>
</div>
)}
</div>
);
}
export interface SheetProps extends Omit<SheetPanelProps, 'onClose' | 'titleId' | 'descriptionId' | 'footer'> {
trigger?: React.ReactElement;
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
footer?: React.ReactNode | ((close: () => void) => React.ReactNode);
}
/**
* A panel that slides in from an edge: filters, carts, menus, details. Modal, on the native <dialog>.
* It slides back out before it closes, its parts arrive one after another.
* <Sheet side="right" trigger={<Button>Filters</Button>} title="Filters">…</Sheet>
*/
export function Sheet({ trigger, open: openProp, defaultOpen, onOpenChange, side = 'right', footer, children, ...panel }: SheetProps) {
const [open, setOpen] = useDialogState(openProp, defaultOpen, onOpenChange);
const [modal, present] = useModalMotion(open, setOpen, { motion: side });
const id = useId();
const close = () => setOpen(false);
return (
<>
{trigger && withTrigger(trigger, open, setOpen, id)}
<DialogMotionStyle />
<dialog
{...modal}
id={id}
aria-labelledby={`${id}-t`}
aria-describedby={panel.description ? `${id}-d` : undefined}
className={cn('kitfx-dialog overflow-visible bg-transparent p-0 text-left outline-none data-[closing]:pointer-events-none', PLACE[side], scrim)}
>
{present && (
<SheetPanel {...panel} side={side} titleId={`${id}-t`} descriptionId={`${id}-d`} onClose={close} footer={typeof footer === 'function' ? footer(close) : footer}>
{children}
</SheetPanel>
)}
</dialog>
</>
);
}Häufige Fragen
Wie installiere ich Sheet?
Mit aiw add @ai-website/sheet. Der Befehl legt die Datei components/ui/sheet.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind icon und dialog. Weitere npm-Pakete braucht sie nicht.
Welche Varianten hat Sheet?
2 Varianten, Filter von rechts und Links und unten.
Kann ich die Texte von Sheet übersetzen?
Ja. Sheet bringt 1 eigene Texte auf Deutsch und Englisch mit, etwa „Schließen“. Die Sprache folgt dem KitProvider, einzelne Texte ersetzt du über die Prop labels.
Kann ein KI-Assistent Sheet einbauen?
Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Sheet mit aiw_components und installieren sie mit aiw_add @ai-website/sheet.