Actions
Drawer
A Sheet that rises from the bottom edge of the screen.
| Class name | Type | Description |
|---|---|---|
sheetplf:sheet | Component | A Modal dialog as a panel |
sheet-handleplf:sheet-handle | Part | A decorative handle |
sheet-headerplf:sheet-header | Part | Title and description, centered below md |
sheet-titleplf:sheet-title | Part | The panel's heading |
sheet-descriptionplf:sheet-description | Part | Muted text under the title |
sheet-bodyplf:sheet-body | Part | The scrolling content |
sheet-footerplf:sheet-footer | Part | Actions stacked in source order, primary first |
sheet-bottomplf:sheet-bottom | Placement | From the bottom edge |
<button class="btn btn-outline" data-plf-trigger="modal:open" data-plf-target="drawer-default">Open drawer</button>
<dialog class="modal sheet sheet-bottom" data-plf-modal id="drawer-default" aria-describedby="drawer-default-description" data-plf-close-on-backdrop>
<div class="sheet-handle"></div>
<header class="sheet-header">
<h2 class="sheet-title">Archive project?</h2>
<p class="sheet-description" id="drawer-default-description">The project moves to Archive. You can restore it at any time.</p>
</header>
<footer class="sheet-footer">
<button class="btn btn-error" data-plf-trigger="modal:close">Archive project</button>
<button class="btn btn-outline" data-plf-trigger="modal:close" autofocus>Cancel</button>
</footer>
</dialog><button class="plf:btn plf:btn-outline" data-plf-trigger="modal:open" data-plf-target="drawer-default">Open drawer</button>
<dialog class="plf:modal plf:sheet plf:sheet-bottom" data-plf-modal id="drawer-default" aria-describedby="drawer-default-description" data-plf-close-on-backdrop>
<div class="plf:sheet-handle"></div>
<header class="plf:sheet-header">
<h2 class="plf:sheet-title">Archive project?</h2>
<p class="plf:sheet-description" id="drawer-default-description">The project moves to Archive. You can restore it at any time.</p>
</header>
<footer class="plf:sheet-footer">
<button class="plf:btn plf:btn-error" data-plf-trigger="modal:close">Archive project</button>
<button class="plf:btn plf:btn-outline" data-plf-trigger="modal:close" autofocus>Cancel</button>
</footer>
</dialog>'use client';
import { useState } from 'react';
import { Button, Sheet, SheetDescription, SheetFooter, SheetHandle, SheetHeader, SheetTitle } from '@pelatform/react';
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>Open drawer</Button>
<Sheet side="bottom" open={open} onOpenChange={setOpen} closeOnBackdrop>
<SheetHandle />
<SheetHeader>
<SheetTitle>Archive project?</SheetTitle>
<SheetDescription>The project moves to Archive. You can restore it at any time.</SheetDescription>
</SheetHeader>
<SheetFooter>
<Button color="error" onClick={() => setOpen(false)}>Archive project</Button>
<Button variant="outline" autoFocus onClick={() => setOpen(false)}>Cancel</Button>
</SheetFooter>
</Sheet>
</>
);
}'use client';
import { useState } from 'react';
import { Button, Sheet, SheetDescription, SheetFooter, SheetHandle, SheetHeader, SheetTitle } from '@pelatform/react/prefixed';
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>Open drawer</Button>
<Sheet side="bottom" open={open} onOpenChange={setOpen} closeOnBackdrop>
<SheetHandle />
<SheetHeader>
<SheetTitle>Archive project?</SheetTitle>
<SheetDescription>The project moves to Archive. You can restore it at any time.</SheetDescription>
</SheetHeader>
<SheetFooter>
<Button color="error" onClick={() => setOpen(false)}>Archive project</Button>
<Button variant="outline" autoFocus onClick={() => setOpen(false)}>Cancel</Button>
</SheetFooter>
</Sheet>
</>
);
}The handle cannot be dragged, so always include a visible close action such as Cancel.
With a form
Give the form method="dialog", which closes the drawer when it is submitted, and link the footer’s submit button to it with the form attribute.
<button class="btn btn-outline" data-plf-trigger="modal:open" data-plf-target="drawer-form">Edit profile</button>
<dialog class="modal sheet sheet-bottom" data-plf-modal id="drawer-form" aria-describedby="drawer-form-description">
<div class="sheet-handle"></div>
<header class="sheet-header">
<h2 class="sheet-title">Edit profile</h2>
<p class="sheet-description" id="drawer-form-description">Changes apply after you save.</p>
</header>
<div class="sheet-body">
<form class="flex flex-col gap-4" id="drawer-form-profile" method="dialog">
<div class="field">
<label class="field-label" for="drawer-form-name">Name</label>
<input class="input" id="drawer-form-name" name="name" type="text" autocomplete="name" value="Jordan Rivera" required>
</div>
<div class="field">
<label class="field-label" for="drawer-form-username">Username</label>
<input class="input" id="drawer-form-username" name="username" type="text" autocomplete="username" value="jordan">
</div>
</form>
</div>
<footer class="sheet-footer">
<button class="btn btn-primary" type="submit" form="drawer-form-profile">Save changes</button>
<button class="btn btn-outline" data-plf-trigger="modal:close">Cancel</button>
</footer>
</dialog>
<script>
const form = document.getElementById('drawer-form-profile');
form.addEventListener('submit', () => {
// Save new FormData(form) here; the form then closes the drawer.
});
</script><button class="plf:btn plf:btn-outline" data-plf-trigger="modal:open" data-plf-target="drawer-form">Edit profile</button>
<dialog class="plf:modal plf:sheet plf:sheet-bottom" data-plf-modal id="drawer-form" aria-describedby="drawer-form-description">
<div class="plf:sheet-handle"></div>
<header class="plf:sheet-header">
<h2 class="plf:sheet-title">Edit profile</h2>
<p class="plf:sheet-description" id="drawer-form-description">Changes apply after you save.</p>
</header>
<div class="plf:sheet-body">
<form class="plf:flex plf:flex-col plf:gap-4" id="drawer-form-profile" method="dialog">
<div class="plf:field">
<label class="plf:field-label" for="drawer-form-name">Name</label>
<input class="plf:input" id="drawer-form-name" name="name" type="text" autocomplete="name" value="Jordan Rivera" required>
</div>
<div class="plf:field">
<label class="plf:field-label" for="drawer-form-username">Username</label>
<input class="plf:input" id="drawer-form-username" name="username" type="text" autocomplete="username" value="jordan">
</div>
</form>
</div>
<footer class="plf:sheet-footer">
<button class="plf:btn plf:btn-primary" type="submit" form="drawer-form-profile">Save changes</button>
<button class="plf:btn plf:btn-outline" data-plf-trigger="modal:close">Cancel</button>
</footer>
</dialog>
<script>
const form = document.getElementById('drawer-form-profile');
form.addEventListener('submit', () => {
// Save new FormData(form) here; the form then closes the drawer.
});
</script>'use client';
import { useId, useState, type FormEvent } from 'react';
import { Button, Field, Input, Sheet, SheetBody, SheetDescription, SheetFooter, SheetHandle, SheetHeader, SheetTitle } from '@pelatform/react';
export function Example() {
const [open, setOpen] = useState(false);
const form = useId();
function save(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
// Save new FormData(event.currentTarget) here.
setOpen(false);
}
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>Edit profile</Button>
<Sheet side="bottom" open={open} onOpenChange={setOpen}>
<SheetHandle />
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>Changes apply after you save.</SheetDescription>
</SheetHeader>
<SheetBody>
<form className="flex flex-col gap-4" id={form} onSubmit={save}>
<Field label="Name">
<Input name="name" type="text" autoComplete="name" defaultValue="Jordan Rivera" required />
</Field>
<Field label="Username">
<Input name="username" type="text" autoComplete="username" defaultValue="jordan" />
</Field>
</form>
</SheetBody>
<SheetFooter>
<Button color="primary" type="submit" form={form}>Save changes</Button>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
</SheetFooter>
</Sheet>
</>
);
}'use client';
import { useId, useState, type FormEvent } from 'react';
import { Button, Field, Input, Sheet, SheetBody, SheetDescription, SheetFooter, SheetHandle, SheetHeader, SheetTitle } from '@pelatform/react/prefixed';
export function Example() {
const [open, setOpen] = useState(false);
const form = useId();
function save(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
// Save new FormData(event.currentTarget) here.
setOpen(false);
}
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>Edit profile</Button>
<Sheet side="bottom" open={open} onOpenChange={setOpen}>
<SheetHandle />
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>Changes apply after you save.</SheetDescription>
</SheetHeader>
<SheetBody>
<form className="plf:flex plf:flex-col plf:gap-4" id={form} onSubmit={save}>
<Field label="Name">
<Input name="name" type="text" autoComplete="name" defaultValue="Jordan Rivera" required />
</Field>
<Field label="Username">
<Input name="username" type="text" autoComplete="username" defaultValue="jordan" />
</Field>
</form>
</SheetBody>
<SheetFooter>
<Button color="primary" type="submit" form={form}>Save changes</Button>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
</SheetFooter>
</Sheet>
</>
);
}Scrolling body
Give a body without focusable content tabindex="0", role="region" and an aria-label.
<button class="btn btn-outline" data-plf-trigger="modal:open" data-plf-target="drawer-scroll">Release notes</button>
<dialog class="modal sheet sheet-bottom" data-plf-modal id="drawer-scroll" aria-describedby="drawer-scroll-description">
<header class="sheet-header">
<h2 class="sheet-title">Release notes</h2>
<p class="sheet-description" id="drawer-scroll-description">Everything that changed this month.</p>
</header>
<div class="sheet-body" tabindex="0" role="region" aria-label="Release notes">
<div class="flex flex-col gap-4">
<p>Search remembers your recent queries.</p>
<p>Tables keep their header visible while you scroll.</p>
<p>File uploads accept files from the clipboard.</p>
<p>Date pickers keep keyboard focus visible.</p>
<p>Notifications pause while you hover them.</p>
<p>Sidebars remember their collapsed state.</p>
<p>Charts follow the dark theme.</p>
<p>Thank you for your feedback.</p>
</div>
</div>
<footer class="sheet-footer">
<button class="btn btn-outline" data-plf-trigger="modal:close">Done</button>
</footer>
</dialog><button class="plf:btn plf:btn-outline" data-plf-trigger="modal:open" data-plf-target="drawer-scroll">Release notes</button>
<dialog class="plf:modal plf:sheet plf:sheet-bottom" data-plf-modal id="drawer-scroll" aria-describedby="drawer-scroll-description">
<header class="plf:sheet-header">
<h2 class="plf:sheet-title">Release notes</h2>
<p class="plf:sheet-description" id="drawer-scroll-description">Everything that changed this month.</p>
</header>
<div class="plf:sheet-body" tabindex="0" role="region" aria-label="Release notes">
<div class="plf:flex plf:flex-col plf:gap-4">
<p>Search remembers your recent queries.</p>
<p>Tables keep their header visible while you scroll.</p>
<p>File uploads accept files from the clipboard.</p>
<p>Date pickers keep keyboard focus visible.</p>
<p>Notifications pause while you hover them.</p>
<p>Sidebars remember their collapsed state.</p>
<p>Charts follow the dark theme.</p>
<p>Thank you for your feedback.</p>
</div>
</div>
<footer class="plf:sheet-footer">
<button class="plf:btn plf:btn-outline" data-plf-trigger="modal:close">Done</button>
</footer>
</dialog>'use client';
import { useState } from 'react';
import { Button, Sheet, SheetBody, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from '@pelatform/react';
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>Release notes</Button>
<Sheet side="bottom" open={open} onOpenChange={setOpen}>
<SheetHeader>
<SheetTitle>Release notes</SheetTitle>
<SheetDescription>Everything that changed this month.</SheetDescription>
</SheetHeader>
<SheetBody tabIndex={0} role="region" aria-label="Release notes">
<div className="flex flex-col gap-4">
<p>Search remembers your recent queries.</p>
<p>Tables keep their header visible while you scroll.</p>
<p>File uploads accept files from the clipboard.</p>
<p>Date pickers keep keyboard focus visible.</p>
<p>Notifications pause while you hover them.</p>
<p>Sidebars remember their collapsed state.</p>
<p>Charts follow the dark theme.</p>
<p>Thank you for your feedback.</p>
</div>
</SheetBody>
<SheetFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Done</Button>
</SheetFooter>
</Sheet>
</>
);
}'use client';
import { useState } from 'react';
import { Button, Sheet, SheetBody, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from '@pelatform/react/prefixed';
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>Release notes</Button>
<Sheet side="bottom" open={open} onOpenChange={setOpen}>
<SheetHeader>
<SheetTitle>Release notes</SheetTitle>
<SheetDescription>Everything that changed this month.</SheetDescription>
</SheetHeader>
<SheetBody tabIndex={0} role="region" aria-label="Release notes">
<div className="plf:flex plf:flex-col plf:gap-4">
<p>Search remembers your recent queries.</p>
<p>Tables keep their header visible while you scroll.</p>
<p>File uploads accept files from the clipboard.</p>
<p>Date pickers keep keyboard focus visible.</p>
<p>Notifications pause while you hover them.</p>
<p>Sidebars remember their collapsed state.</p>
<p>Charts follow the dark theme.</p>
<p>Thank you for your feedback.</p>
</div>
</SheetBody>
<SheetFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Done</Button>
</SheetFooter>
</Sheet>
</>
);
}JavaScript
Drawer needs Modal’s JavaScript. The Full script (pelatform-ui.min.js) starts every component on page load, with no code of your own; with ESM imports, call init() yourself, as in Installation. For markup you add later, see Dynamic content.
<link rel="stylesheet" href="pelatform-ui.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.min.css">
<link rel="stylesheet" href="components/button.min.css">
<link rel="stylesheet" href="components/form.min.css">
<link rel="stylesheet" href="components/modal.min.css">
<link rel="stylesheet" href="components/sheet.min.css">
<link rel="stylesheet" href="components/drawer.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/modal.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script><link rel="stylesheet" href="pelatform-ui.prefixed.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.prefixed.min.css">
<link rel="stylesheet" href="components/button.prefixed.min.css">
<link rel="stylesheet" href="components/form.prefixed.min.css">
<link rel="stylesheet" href="components/modal.prefixed.min.css">
<link rel="stylesheet" href="components/sheet.prefixed.min.css">
<link rel="stylesheet" href="components/drawer.prefixed.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/modal.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script>| Variable | Default |
|---|---|
--plf-drawer-bg, --plf-drawer-color |
--plf-modal-bg and --plf-modal-color, then surface and text |
--plf-drawer-border |
Top edge: --plf-modal-border, then subtle border |
--plf-drawer-radius |
Extra large radius (12px), on the top corners |
--plf-drawer-shadow |
--plf-modal-shadow, then large shadow |
--plf-drawer-backdrop-bg |
--plf-modal-backdrop-bg, then --plf-color-backdrop |
--plf-drawer-padding |
Space 4 (16px) |
--plf-drawer-content-width |
32rem, the centered content width |
--plf-drawer-max-height |
80dvh |
--plf-drawer-handle-color |
--plf-color-surface-muted |
The panel adds the screen’s safe-area insets, which browsers report only when the viewport meta tag includes viewport-fit=cover.