Sheet
Extends the dialog pattern to display content that slides in from the edge of the screen.
Built on native <dialog> + showModal() with CSS-only slide animations via @starting-style.
Supports four sides: right, left, top, bottom.
Right
Default side. The sheet slides in from the right edge — commonly used for edit-profile or settings panels.
<!-- Trigger -->
<button class="btn" data-variant="outline"
data-sheet-trigger="sheet-right"
aria-haspopup="dialog">Open Right Sheet</button>
<!-- Sheet (place as direct child of body) -->
<dialog id="sheet-right" class="sheet" data-side="right"
role="dialog" aria-modal="true"
aria-labelledby="sheet-right-title">
<div class="sheet-content">
<div class="sheet-header">
<h2 class="sheet-title" id="sheet-right-title">Edit Profile</h2>
<p class="sheet-description">Make changes to your profile here. Click save when you're done.</p>
</div>
<div class="sheet-body">
<div style="display:flex;flex-direction:column;gap:1rem;">
<div>
<label class="label" for="sheet-name">Name</label>
<input id="sheet-name" class="input" type="text" value="Cody Lindley">
</div>
<div>
<label class="label" for="sheet-username">Username</label>
<input id="sheet-username" class="input" type="text" value="@codylindley">
</div>
</div>
</div>
<div class="sheet-footer">
<button class="btn" data-variant="outline" data-sheet-close>Cancel</button>
<button class="btn" data-variant="default" data-sheet-close>Save changes</button>
</div>
</div>
<button class="sheet-close-x" data-sheet-close aria-label="Close">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M18 6 6 18M6 6l12 12"/>
</svg>
</button>
</dialog>
Left
Slides in from the left edge — ideal for navigation menus or sidebar-style panels.
<!-- Trigger -->
<button class="btn" data-variant="outline"
data-sheet-trigger="sheet-left"
aria-haspopup="dialog">Open Left Sheet</button>
<!-- Sheet -->
<dialog id="sheet-left" class="sheet" data-side="left"
role="dialog" aria-modal="true"
aria-labelledby="sheet-left-title">
<div class="sheet-content">
<div class="sheet-header">
<h2 class="sheet-title" id="sheet-left-title">Navigation</h2>
<p class="sheet-description">Browse sections of the application.</p>
</div>
<div class="sheet-body">
<nav style="display:flex;flex-direction:column;gap:0.25rem;">
<a href="#" style="display:flex;align-items:center;gap:0.5rem;padding:0.5rem 0.75rem;border-radius:var(--radius-lg);font-size:0.875rem;text-decoration:none;color:var(--foreground);transition:background 150ms;" onmouseover="this.style.background='var(--accent)'" onmouseout="this.style.background=''">Home</a>
<a href="#" style="display:flex;align-items:center;gap:0.5rem;padding:0.5rem 0.75rem;border-radius:var(--radius-lg);font-size:0.875rem;text-decoration:none;color:var(--foreground);transition:background 150ms;" onmouseover="this.style.background='var(--accent)'" onmouseout="this.style.background=''">Dashboard</a>
<a href="#" style="display:flex;align-items:center;gap:0.5rem;padding:0.5rem 0.75rem;border-radius:var(--radius-lg);font-size:0.875rem;text-decoration:none;color:var(--foreground);transition:background 150ms;" onmouseover="this.style.background='var(--accent)'" onmouseout="this.style.background=''">Settings</a>
<a href="#" style="display:flex;align-items:center;gap:0.5rem;padding:0.5rem 0.75rem;border-radius:var(--radius-lg);font-size:0.875rem;text-decoration:none;color:var(--foreground);transition:background 150ms;" onmouseover="this.style.background='var(--accent)'" onmouseout="this.style.background=''">Help</a>
</nav>
</div>
</div>
<button class="sheet-close-x" data-sheet-close aria-label="Close">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M18 6 6 18M6 6l12 12"/>
</svg>
</button>
</dialog>
Top
Slides down from the top edge — useful for notifications or announcement banners.
<!-- Trigger -->
<button class="btn" data-variant="outline"
data-sheet-trigger="sheet-top"
aria-haspopup="dialog">Open Top Sheet</button>
<!-- Sheet -->
<dialog id="sheet-top" class="sheet" data-side="top"
role="dialog" aria-modal="true"
aria-labelledby="sheet-top-title">
<div class="sheet-content">
<div class="sheet-header">
<h2 class="sheet-title" id="sheet-top-title">Notifications</h2>
<p class="sheet-description">You have 3 unread messages.</p>
</div>
<div class="sheet-body">
<div style="display:flex;flex-direction:column;gap:0.75rem;">
<div style="display:flex;align-items:flex-start;gap:0.75rem;padding:0.75rem;border-radius:var(--radius-lg);background:color-mix(in oklch, var(--muted), transparent 50%);">
<div style="width:0.5rem;height:0.5rem;border-radius:9999px;background:var(--primary);margin-top:0.375rem;flex-shrink:0;"></div>
<div><p style="font-size:0.875rem;font-weight:500;">New comment on your post</p><p style="font-size:0.75rem;color:var(--muted-foreground);margin-top:0.125rem;">2 minutes ago</p></div>
</div>
<div style="display:flex;align-items:flex-start;gap:0.75rem;padding:0.75rem;border-radius:var(--radius-lg);background:color-mix(in oklch, var(--muted), transparent 50%);">
<div style="width:0.5rem;height:0.5rem;border-radius:9999px;background:var(--primary);margin-top:0.375rem;flex-shrink:0;"></div>
<div><p style="font-size:0.875rem;font-weight:500;">Deployment successful</p><p style="font-size:0.75rem;color:var(--muted-foreground);margin-top:0.125rem;">15 minutes ago</p></div>
</div>
<div style="display:flex;align-items:flex-start;gap:0.75rem;padding:0.75rem;border-radius:var(--radius-lg);background:color-mix(in oklch, var(--muted), transparent 50%);">
<div style="width:0.5rem;height:0.5rem;border-radius:9999px;background:var(--muted-foreground);margin-top:0.375rem;flex-shrink:0;"></div>
<div><p style="font-size:0.875rem;font-weight:500;">Team invitation accepted</p><p style="font-size:0.75rem;color:var(--muted-foreground);margin-top:0.125rem;">1 hour ago</p></div>
</div>
</div>
</div>
<div class="sheet-footer">
<button class="btn" data-variant="outline" data-size="sm" data-sheet-close>Dismiss all</button>
</div>
</div>
<button class="sheet-close-x" data-sheet-close aria-label="Close">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M18 6 6 18M6 6l12 12"/>
</svg>
</button>
</dialog>
Bottom
Slides up from the bottom edge — great for cookie consent, action sheets, or preference panels.
<!-- Trigger -->
<button class="btn" data-variant="outline"
data-sheet-trigger="sheet-bottom"
aria-haspopup="dialog">Open Bottom Sheet</button>
<!-- Sheet -->
<dialog id="sheet-bottom" class="sheet" data-side="bottom"
role="dialog" aria-modal="true"
aria-labelledby="sheet-bottom-title">
<div class="sheet-content">
<div class="sheet-header">
<h2 class="sheet-title" id="sheet-bottom-title">Cookie Preferences</h2>
<p class="sheet-description">Manage your cookie settings. You can enable or disable different types of cookies below.</p>
</div>
<div class="sheet-body">
<div style="display:flex;flex-direction:column;gap:1rem;">
<div style="display:flex;align-items:center;justify-content:space-between;">
<div><p style="font-size:0.875rem;font-weight:500;">Essential Cookies</p><p style="font-size:0.75rem;color:var(--muted-foreground);margin-top:0.125rem;">Required for the website to function.</p></div>
<span style="font-size:0.75rem;color:var(--muted-foreground);">Always on</span>
</div>
<div style="display:flex;align-items:center;justify-content:space-between;">
<div><p style="font-size:0.875rem;font-weight:500;">Analytics Cookies</p><p style="font-size:0.75rem;color:var(--muted-foreground);margin-top:0.125rem;">Help us improve our website.</p></div>
<input type="checkbox" checked>
</div>
<div style="display:flex;align-items:center;justify-content:space-between;">
<div><p style="font-size:0.875rem;font-weight:500;">Marketing Cookies</p><p style="font-size:0.75rem;color:var(--muted-foreground);margin-top:0.125rem;">Used for targeted advertising.</p></div>
<input type="checkbox">
</div>
</div>
</div>
<div class="sheet-footer">
<button class="btn" data-variant="outline" data-sheet-close>Cancel</button>
<button class="btn" data-variant="default" data-sheet-close>Save preferences</button>
</div>
</div>
<button class="sheet-close-x" data-sheet-close aria-label="Close">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M18 6 6 18M6 6l12 12"/>
</svg>
</button>
</dialog>
CSS view file
/* -- Sheet component ------------------------------------------- */
@layer components {
/* -- Base -------------------------------------------------- */
dialog.sheet {
border: none;
border-radius: 0;
background-color: var(--background);
color: var(--foreground);
padding: 0;
margin: 0;
max-width: none;
max-height: none;
opacity: 0;
transition: opacity 300ms ease, transform 300ms ease, display 300ms allow-discrete;
&[open] { opacity: 1; }
/* -- Side: right (default) --------------------------------- */
&, &[data-side="right"] {
position: fixed;
top: 0; right: 0; bottom: 0;
left: auto;
width: 24rem;
max-width: 100vw;
max-height: 100vh;
height: 100%;
border-left: 1px solid var(--border);
transform: translateX(100%);
}
&[open], &[data-side="right"][open] {
transform: translateX(0);
}
/* -- Side: left -------------------------------------------- */
&[data-side="left"] {
position: fixed;
top: 0; left: 0; bottom: 0;
right: auto;
width: 24rem;
max-width: 100vw;
max-height: 100vh;
height: 100%;
border-left: none;
border-right: 1px solid var(--border);
transform: translateX(-100%);
&[open] { transform: translateX(0); }
}
/* -- Side: top --------------------------------------------- */
&[data-side="top"] {
position: fixed;
top: 0; left: 0; right: 0;
bottom: auto;
width: 100%;
max-width: 100vw;
max-height: 100vh;
height: auto;
border-left: none;
border-bottom: 1px solid var(--border);
transform: translateY(-100%);
&[open] { transform: translateY(0); }
}
/* -- Side: bottom ------------------------------------------ */
&[data-side="bottom"] {
position: fixed;
bottom: 0; left: 0; right: 0;
top: auto;
width: 100%;
max-width: 100vw;
max-height: 100vh;
height: auto;
border-left: none;
border-top: 1px solid var(--border);
transform: translateY(100%);
&[open] { transform: translateY(0); }
}
/* -- Backdrop ---------------------------------------------- */
&::backdrop {
background: oklch(0 0 0 / 0);
backdrop-filter: blur(0px);
transition: all 300ms ease, display 300ms allow-discrete;
}
&[open]::backdrop {
background: oklch(0 0 0 / 0.45);
backdrop-filter: blur(3px);
}
}
@starting-style {
dialog.sheet[open],
dialog.sheet[data-side="right"][open] {
opacity: 0;
transform: translateX(100%);
}
dialog.sheet[data-side="left"][open] {
opacity: 0;
transform: translateX(-100%);
}
dialog.sheet[data-side="top"][open] {
opacity: 0;
transform: translateY(-100%);
}
dialog.sheet[data-side="bottom"][open] {
opacity: 0;
transform: translateY(100%);
}
dialog.sheet[open]::backdrop {
background: oklch(0 0 0 / 0);
backdrop-filter: blur(0px);
}
}
/* -- Content sections -------------------------------------- */
.sheet-content { padding: 1.5rem; position: relative; }
.sheet-header { margin-bottom: 1rem; padding-right: 2rem; }
.sheet-title { font-size: 1.0625rem; font-weight: 600; margin: 0 0 0.375rem; letter-spacing: -0.01em; }
.sheet-description { font-size: 0.875rem; color: var(--muted-foreground); margin: 0; line-height: 1.6; }
.sheet-body { margin-top: 1rem; }
.sheet-footer { display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 1.5rem; }
/* -- Close button ------------------------------------------ */
.sheet-close-x {
position: absolute;
top: 1rem; right: 1rem;
width: 1.75rem; height: 1.75rem;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
color: var(--muted-foreground);
border-radius: var(--radius-sm);
cursor: pointer;
transition: color 150ms, background-color 150ms;
&:hover {
color: var(--foreground);
background-color: var(--accent);
}
}
}
JavaScript view file
Identical pattern to Dialog. Wire triggers via data-sheet-trigger, close buttons via data-sheet-close, and backdrop click. Targets dialog.sheet elements.
// -- Sheet ----------------------------------------------------
// Wires [data-sheet-trigger] buttons to <dialog class="sheet"> elements.
function init() {
document.querySelectorAll('[data-sheet-trigger]:not([data-init])').forEach((trigger) => {
trigger.dataset.init = '';
const sheet = document.getElementById(trigger.dataset.sheetTrigger);
if (!sheet) return;
trigger.addEventListener('click', () => {
sheet._trigger = trigger;
sheet.showModal();
});
});
document.querySelectorAll('dialog.sheet:not([data-init])').forEach((sheet) => {
sheet.dataset.init = '';
sheet.addEventListener('click', (e) => {
if (e.target === sheet) sheet.close();
});
sheet.querySelectorAll('[data-sheet-close]').forEach((btn) => {
btn.addEventListener('click', () => { sheet.close(); });
});
sheet.addEventListener('close', () => {
if (sheet._trigger) sheet._trigger.focus();
});
});
}
init();
new MutationObserver(init).observe(document, { childList: true, subtree: true });