Guides

Overlays & full-screen

Dialog, Sheet, Drawer and full-screen views are the four surfaces a user can be shifted onto. They look similar but each has one clear job. This is how we pick between them at Yuno.

The surface ladder

This is the decision, and it runs BEFORE you pick components. Walk it down and stop at the first yes. Every question is about something you can count in the file, which is deliberate: the old first question was "do you need the list behind as context?", and nobody has ever answered no to that.

  1. 1
    Step 0 — Does it need an address? Is it shared by link, reached from more than one place, or does the browser back button have to work on it?

    Use: Full-screen. It is a route, not an overlay, so nothing below this matters. Rule details, payment details, dispute details.

  2. 2
    Step 1a — Does it hold a collection you browse? A Table, a search or filter control, or more than five selectable rows.

    Use: Sheet, right side. Any single yes in step 1 is enough; you do not need all three.

  3. 3
    Step 1b — Does it scroll on its own? An overflow-auto or a max-h on the body.

    Use: Sheet. A body that needs a scroll is a body that did not fit, and a Sheet is full height for free.

  4. 4
    Step 1c — Does it ask for more than three fields? Count every Input, Select, Textarea, Combobox, RadioGroup and date picker.

    Use: Sheet. Three is the line: four is a form, and a form in a centred modal is cramped.

  5. 5
    Step 2 — All three said no.

    Use: Dialog. A short blocking question or one to three fields. This is the DEFAULT, not the fallback: most overlays land here.

  6. 6
    The one exception — does it open from inside a Sheet?

    Use: Dialog, and only here may it grow past the canonical width. Sheet-on-Sheet is forbidden and full-screen would throw away the parent Sheet's state, so Dialog is the only surface left. If you can reach the same surface from a page, this exception does not apply to you.

  7. 7
    Only for mobile prototypes — is a bottom sheet with drag-to-dismiss the native gesture here?

    Use: Drawer. The Dashboard is desktop-first, so this is rare. On desktop there is no gesture and the expectation is a side panel: use a Sheet.

The ladder is checked, not just written
npm run kit:surfaces walks a prototype, finds every DialogContent and SheetContent, and counts these same signals inside them: a Table, a search control, an overflow class, the number of fields, whether it sits inside a Sheet. It prints a verdict per overlay with file and line, and exits non-zero when one is wrong. That is only possible because the ladder is written over things you can count rather than over intent. Nobody, human or model, can grep "does the user need the list behind as context" — which is exactly why that question never decided anything.

Quick pick

One line to decide, if you're in a hurry. The rest of the page is the reasoning.

DialogOverlay component

A short, blocking decision. Confirm, cancel, quick form of 1 to 3 fields. Modal, centered, small.

SheetOverlay component

A side or bottom panel to create, edit or view details while the list behind stays as context. Desktop canonical for both short-to-medium create flows (payment link, rule, blocklist) and row-detail editing.

DrawerOverlay component

A mobile-first bottom sheet with drag-to-dismiss and snap points. In the Yuno Dashboard (desktop-first) it's rare, use it only in mobile surfaces.

Full-screen viewRoute, not a component

A page of its own with URL, breadcrumbs and its own layout. Rule details, payment details, dispute details. Use when the object is complex enough to earn its own screen.

How they compare

The characteristics that push you toward one and away from another.

CharacteristicDialogSheetDrawerFull-screen
PurposeConfirm, decide, quick 1-3 field form.Create, edit or view details while keeping list context.Mobile bottom sheet with gesture affordance.Explore or edit a complex object as its own page.
Blocks the appYes, modal.Yes, with a dimmer.Yes, with a dimmer + scale.No, it's a route.
AnchorCentered.Right (canonical) / left / top / bottom.Bottom by default.N/A, full viewport.
SizeSmall, content-driven.Up to 768px wide (right/left) or full width (top/bottom).Bottom, supports snap points.100% of the main region.
DismissESC, overlay click, Cancel.ESC, overlay click, X button.Drag down, ESC, overlay click.Back / breadcrumb / route change.
Has its own URLNo.No.No.Yes, and it's shareable.
Preserves list contextYes, list stays visible behind the modal.Yes, list stays visible next to the panel.Partially, list is dimmed underneath.No, list is replaced by the detail page.
Yuno usageConfirmations, quick actions.Long create forms (payment link, rule, blocklist), row-level details, edit-in-place, filters.Rare on Dashboard (mobile surfaces only).Rule details, payment details, dispute details.

About full-screen

Full-screen isn't a component in the kit. It's a routing decision.

Full-screen = a route with its own page.
There is no <FullScreen> import. You create a new route (e.g. /rules/[id]) and use the Base template as the shell (Sidebar + TopBar stay, main is replaced). The Detail page template will cover the standard structure: breadcrumb + page header + tabs + content. Because it's a real route, the URL is shareable, the browser back button works, and deep-linking is trivial. Reach for full-screen when the object earns its own address, not when a Sheet would keep the user better oriented.

Multi-step within a single component

All four surfaces (Sheet, Drawer, Dialog, full-screen view) support internal multi-step flows. The header can host a back arrow (ArrowLeft) so the user moves between steps without leaving the component. Same instance, different body content.

Sheet

The back arrow lives in the SheetHeader (left of the SheetTitle). Body content changes as the user progresses. Use when each step is a medium form (multiple inputs, some data, a section or two). Typical for create flows like rule → condition → operand, or payment link → recipient → amount.

Drawer

The back arrow lives in the DrawerHeader. Body content swaps between steps just like Sheet. Uncommon in the Yuno Dashboard (desktop-first), but valid when a mobile prototype needs a multi-step bottom sheet with drag-to-dismiss between steps.

Dialog

The Dialog header can carry a back arrow for a short multi-step flow. Use when each step is small (2-3 inputs or a simple choice), like 'choose action → confirm details'. If any single step needs a longer form, jump to Sheet.

Full-screen view

A wizard route or step-by-step page. The back arrow lives in the PageHeader or is replaced by a Stepper. Use when steps carry long forms, lots of content, or dense info that would feel cramped inside a Sheet.

This is not stacking overlays
Multi-step ≠ stacking. It's the SAME open instance whose body swaps content between steps. A Sheet with a back arrow is one Sheet, not two Sheets on top of each other. Same for Drawer, Dialog and full-screen. The stacking rule (max 2 layers, only Dialog on top of Sheet) still applies to actually opening a second overlay.

See it in action

Two real multi-step flows. The Sheet on the left carries a heavy form across 3 steps, the Dialog on the right handles a short 2-step decision. Same navigation pattern (back arrow in header, step indicator in footer), different amount of content per step.

How to pick the container for a multi-step flow

The number of steps doesn't decide — the volume of content PER STEP does. Same 3-step flow can live in a Dialog, a Sheet or a full-screen view depending on how heavy each step is.

Content per stepUseExample
Small — 2 to 3 inputs, a single choice, or a short confirmation.DialogChoose action → confirm details. Small enough that the modal doesn't feel cramped.
Medium — a form with multiple inputs, a section or two, some data to review.SheetPayment link create → recipient step (email, name, phone) → amount step (currency, value, description).
Large — long forms, many inputs, dense info, complex configuration.Full-screen viewRule create wizard — conditions with nested logic, actions across multiple providers, priority + scheduling.

Canonical Yuno mapping

Real Dashboard scenarios and which surface they use today. Use as a starting point when your case matches.

ScenarioSurfaceWhy
Delete a rule, list, route or connectionDialogBlocking decision with a single copy line and 2 buttons. Uses Primary (not destructive red) inside a Dialog — clarity comes from copy.
Quick create (name + 1-2 fields)DialogSmall form, no lateral context needed. The user's attention should collapse to the form.
Create a long-form object (payment link, rule, blocklist)SheetMulti-field forms that don't earn their own page. Right-anchored Sheet gives room for stacked fields and validation without losing the list where the new item will land.
Edit a row without leaving the tableSheetKeeps the table visible as context. Right-anchored is canonical, up to 768px wide.
Filter a listSheetLeft-anchored filter panel. The list stays visible so the user sees the filter effect land.
Rule details (conditions + actions + logs)Full-screenComplex object with tabs, its own scroll, and worth sharing by URL. A Sheet would feel cramped.
Payment details (timeline, events, metadata)Full-screenLong, scrollable, cross-linked from other places (alerts, disputes). Needs its own URL.
Dispute details (case builder + evidence uploads)Full-screenMulti-step, uploads, its own workflow. It's a page, not a panel.
Mobile-only checkout preview inside a prototypeDrawerBottom-anchored with drag-to-dismiss feels native in mobile surfaces. Rare in the Dashboard itself.
Manage secondary items (templates, presets, saved views, member picker) opened from inside a Sheet flowDialog (wide)Sheet-on-Sheet is forbidden and Full-screen would drop the user out of the parent Sheet. Dialog is the only surface that respects both. In this case Dialog can grow up to sm:max-w-4xl and carry non-form content (tables, lists with actions, pickers, tabs). See the exception section below.

AlertDialog: Dialog with stricter dismissal

AlertDialog is not a 5th surface — it's a specialization of Dialog. Same visual anatomy (Header + Body + Footer with borders), same size (~448px, mobile with 16px gutters), same 'Body is required' + 'title-only 99%' + 'never destructive red' rules. The only difference is stricter dismissal semantics + role='alertdialog' for screen readers.

CharacteristicDialogAlertDialog
Dismiss with ESCYes.No — user MUST click Cancel or Action.
Dismiss by clicking overlayYes.No — user MUST click Cancel or Action.
Close X in cornerYes, absolute top-6 right-6.No — intentionally omitted.
Screen reader rolerole='dialog'.role='alertdialog' — announced with more urgency.
Use forConfirmations, quick create, multi-step, all routine decisions (99% of cases).Irreversible actions where accidental dismiss would be harmful (delete workspace, sign out with unsaved changes, discard changes).
AnatomyHeader + Body + Footer with borders (canonical).Same as Dialog — Header + Body + Footer with borders.
Rule of thumb: default to Dialog
Reach for AlertDialog ONLY when the action is genuinely irreversible AND an accidental click-outside would erase user work. Every routine 'Delete rule?', 'Delete list?', 'Delete route?' uses Dialog — those are reversible from the recycle bin / audit log, so a stray click-outside doesn't do damage. If you're not sure, use Dialog.

Exception: management surfaces opened from inside a Sheet

One combination lets Dialog carry more than a small form and grow beyond the default width. It kicks in when the parent surface is already a Sheet AND the user needs a secondary surface to manage saved items (templates, presets, filter views, member lists, etc.). Under that specific setup, Dialog is the only surface left that respects both the stacking rule and the parent flow.

Why it kicks in
  • Sheet-on-Sheet is forbidden by the stacking rule. See the antipatterns below.
  • Full-screen would drop the user out of the parent Sheet, forcing them to reopen and reconfigure everything they had in progress (the export they were composing, the filter they were editing).
  • Dialog-on-Sheet is the only allowed 2-layer stack. It preserves the parent Sheet exactly where the user left it.
What the exception unlocks
  • Content beyond forms: tables, lists with row actions, pickers, previews, tabs, empty states.
  • Width beyond default: up to sm:max-w-4xl (~896px), instead of the standard sm:max-w-md (~448px).
  • Scrollable body: max-h-[60vh] overflow-auto on the DialogBody when the content is longer than the viewport.
When it does NOT apply
  • If the same management surface can be opened from a regular page (not from a Sheet), use the correct surface per the canonical mapping: Full-screen if it deserves a URL, Sheet if it wants to preserve list context on that page.
  • If the surface has its own URL, is deep-linked, or is entered from multiple places, redesign the flow so it opens from a page instead of from a Sheet. Then it becomes Full-screen and this exception is not needed.
  • The Dialog is still transient. When the user closes it, they return to the parent Sheet exactly where they were. If your surface needs to persist independently of the Sheet, it should be Full-screen.
Canonical examples inside Yuno
  • Manage saved templates opened from inside the Export drawer (Payments and Reconciliations).
  • Column preset picker opened from inside a Sheet that configures a table view.
  • Saved filter view manager opened from inside a filter Sheet.
  • Workspace member picker opened from inside a share / permissions Sheet.
Rule of thumb
If you cannot open the surface as a page without losing the parent Sheet flow, this exception applies and Dialog can grow. If you can open it as a page (or from a regular page in the first place), do not take this shortcut. The exception exists because of the stacking constraint, not because Dialog is a good default for management surfaces.
Common mistakes to avoid
  • Using a Dialog for something with more than 3 fields. It ends up cramped, use a Sheet or a full-screen page.
  • Using a Sheet for a confirmation. Feels heavy for a yes/no decision, use a Dialog.
  • Using a Drawer on a desktop-only prototype. There's no gesture and the user expectation is a side panel — use a Sheet.
  • Stacking 3 overlays. Never. Maximum is 2 layers, and only in this exact shape: a Dialog on top of a Sheet. Never Sheet on Sheet, never Dialog on Dialog, never Sheet on top of a Dialog — redesign the flow.
  • Turning a Sheet into a de-facto page. If it has tabs, a scroll, and its own header, it wants to be full-screen.

Related

The three overlay components, and the base template used for full-screen views.

  • DialogSmall blocking modal for confirmations and quick decisions.
  • SheetSide/bottom panel that preserves list context.
  • DrawerBottom sheet with drag-to-dismiss, mobile-first.
  • Base templateShell used for full-screen routes (Sidebar + TopBar + main).