Skip to content

Native Sheet Dialog

A sheet on the native dialog element — no hand-rolled focus trap, and the three things the platform leaves you fixed.

Prominimaleditorial
Category
Patterns
Added
2026-09-21
Deps
none
Updated
2026-09-21

This is a viberdy Pro component

The live preview, the props panel and the write-up are open to everyone. The full source, the AI prompt and CLI install are part of Pro.

Preview

scroll locked · gutter 0px

Refine

Availability

Props

280
Light dismiss
Lock page scroll
No runtime dependencies
"use client";

import { useEffect, useRef, useState } from "react";

/**
 * A sheet built on the NATIVE <dialog> element.
 *
 * The most important decision here is what is NOT in this file: there is no
 * hand-rolled focus trap. `showModal()` already traps focus, makes the rest of
 * the document inert, promotes the element to the top layer so no z-index can
 * bury it, and restores focus to the trigger on close. Every hand-written trap
 * eventually misses something — shadow roots, contenteditable, elements that
 * become disabled while open, the address bar.
 *
 * What the platform does NOT do, and this fixes, is the three things below.
 */
export function NativeSheetDialog({
  label = "Refine",

  // ---------------------------------------------------------------
  // Full source available with a viberdy Pro subscription.
  // https://viberdy.dev/pricing
  // ---------------------------------------------------------------
}
Notes

About this pattern

Most modal components in the wild are several hundred lines of focus management that the browser has shipped natively for years. Calling showModal on a dialog element traps focus, marks the rest of the document inert, promotes the element into the top layer where no stacking context can bury it, and restores focus to the trigger on close — and a hand-written trap will eventually miss shadow roots, contenteditable regions, or controls that become disabled while the dialog is open. So the interesting content here is the small set of things the platform genuinely leaves to you. Locking the page with overflow hidden removes the scrollbar and makes the viewport a dozen or so pixels wider, which lurches the entire page sideways behind the sheet; measuring that width and adding it back as padding is two lines and is the most visible defect in hand-built modals. Closing on transitionend alone leaves the dialog permanently open for anyone with reduced motion enabled or any stylesheet that zeroes the duration, so a timer has to back it up. And light dismiss has to check both ends of the gesture, because a backdrop click reports the dialog itself as its target — check only the click and selecting text inside the sheet then releasing outside it closes the sheet and discards the selection. Escape also needs intercepting through the cancel event, or it closes instantly and skips the exit entirely.

Curator’s note

The best part of this component is the code that is not in it. showModal already gives you the trap, the inert background and the top layer.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

A modal sheet you can drag down to dismiss, with a distance + velocity threshold.

modalsheetdrag

A Cmd+K style searchable command menu with a filtered live list.

commandpalettesearch
Featured
NewPro

A list that dims and blurs every row except the one under the pointer.

listhoverfocus