Skip to content

Sheet

A sheet is a dialog that enters from an edge of the window. On a phone it rises from the bottom, and on a wide screen it slides in from the right so the page beside it stays in view.

Bottom and side

Below 640 px a dialog opens as a bottom sheet. It keeps the header, body and footer of the dialog and adds a grabber, so people can swipe it away with a thumb.

On wider screens a side sheet opens from the right for settings or details that people read beside the page, such as the admins of a job board next to its jobs.

When to use it

Use a side sheet when the page behind it stays part of the task: reading a candidate's details beside the list, or changing who can edit a job board while its jobs stay in view. When the task needs full attention, such as a form that creates something, use a dialog.

A side sheet comes from the right only. The left edge belongs to navigation, and a panel sliding in from there reads as part of it.

Anatomy

Both sheets share the header, body and footer of a dialog. The body scrolls on its own, so a long list does not move the header or the actions.

Grabber
40 by 4 px, 10 px from the top edge
--surface-control, --radius-full
Bottom sheet corners
16 px on the top corners
--radius-2xl
Bottom sheet height
fits the content, up to 92 percent of the window
Full height bottom sheet
the whole window inside the safe areas, for long forms and records
Space under the last control
the bottom safe area, at least 12 px
--space-3
Close button on a phone
44 px
Side sheet width
448 px, full width below 640 px
Side sheet height
the whole window
Surface
raised fill and shadow, with a fine top highlight and rim in the dark theme
--glass-fill-raised, --glass-highlight, --glass-ring-raised, --elevation-3
Scrim
dims the page, no blur
--overlay

Swipe and keys

Drag the grabber or the header down to close a bottom sheet. Dragging the body scrolls its content and leaves the sheet where it is. Short of the threshold, the sheet springs back.

With unsaved entries, a swipe springs back and asks “Discard the changes?” first. Escape and the close button behave as they do in a dialog.

Close by distance
a drag of more than 90 px
Close by speed
a flick faster than 500 px per second
Drag upward
no effect
Escape
closes the sheet, or asks first when entries are unsaved

Motion

A bottom sheet rises from below the window edge in 300 ms and leaves in 200 ms. A side sheet fades in while it travels 40 px from the right, over 200 ms. With reduced motion, both only fade.

Permitted and not permitted

Permitted

  • A side sheet next to the list it describes

    People compare both without closing anything.

  • A grabber on a sheet that swipes away

    It shows where to drag.

Not permitted

  • A side sheet from the left edge

    It reads as navigation.

  • A swipe that closes a sheet holding unsaved entries

    A slip of the thumb loses them. Spring back and ask first.

Accessibility

A sheet is announced and contained like a dialog: role dialog, aria-modal, the title as its label, focus kept inside and returned on close. The grabber is hidden from screen readers, since the close button does the same job.

On a phone the controls in a sheet are at least 44 px high.

NextPopover

Sheet

A dialog that enters from an edge: from the bottom on phones, from the right on wide screens.

  • Kind: component
  • Page: https://design.werklist.com/components/sheet
  • Version: 2.1.2

When to use it

  • The phone form of a dialog, below 640 px
  • A popover that holds a form, on a touch screen below 640 px
  • Details or settings read beside the page, as a side sheet from the right
  • A long list tied to the page, such as members or history

When not to use it

  • A task that needs full attention on a wide screen: use a dialog
  • A side sheet from the left edge: that edge belongs to navigation
  • A second sheet over the first: close it first
  • A task in several steps with a Back button: make it a page

Anatomy

  • Frame, bottom: full width. Height fits the content, up to 92 percent of the window
  • Frame, bottom, full height: the whole window inside the top and bottom safe areas. For large dialogs and forms longer than the window
  • Top corners, bottom sheet: 16 px (--radius-2xl)
  • Grabber: 40 by 4 px, centered, 10 px from the top edge. Only when the sheet can be swiped (--surface-control, --radius-full)
  • Bottom padding: the bottom safe area, at least 12 px (--space-3)
  • Frame, side: right edge, full window height, 448 px wide. Full width below 640 px
  • Header, body, footer: as in a dialog. The close button is 44 px on phones (--space-4, --space-5)
  • Surface: raised fill and shadow, near opaque. In the dark theme a 1 px top highlight and a 0.5 px ring (--glass-fill-raised, --elevation-3, --glass-highlight, --glass-ring-raised)
  • Scrim: no blur (--overlay)

Variants

  • Bottom: Below 640 px, the form every dialog takes. Swipe to close
  • Bottom, full height: Large dialogs and forms longer than the window
  • Side: From the right on wide screens, for details and settings read beside the page

Sizes

  • Side, 448 px wide
  • Bottom, content height up to 92 percent of the window
  • Bottom, full height inside the safe areas

States

  • Opening, bottom: Rises from below the window edge over 300 ms (--duration-slow, --ease-soft)
  • Closing, bottom: Falls back below the edge over 200 ms (--duration-base, --ease-standard)
  • Opening, side: Fades in while traveling 40 px from the right, over 200 ms (--duration-base, --ease-soft)
  • Closing, side: Reverse, over 150 ms (--duration-fast, --ease-standard)
  • Dragging: Follows the finger downward at 70 percent of its travel. Upward travel does nothing. Released short of 90 px and slower than 500 px per second, it springs back
  • Unsaved entries: A swipe springs back and asks Discard the changes? first
  • Reduced motion: A fade only (--duration-fast)
  • Scrolled: As in a dialog: the 1 px line under the header or above the footer while content is hidden behind it (--border-rule)

Keyboard

  • Tab, Shift+Tab: Move within the sheet, wrapping
  • Escape: Closes the sheet, or asks first when entries are unsaved
  • Enter in a single line field: Submits the form

Accessibility

  • As a dialog: role dialog, aria-modal true, labeled by its title, focus kept inside and returned on close, the page behind out of reach
  • The grabber is aria-hidden. The close button, named Close, does the same job
  • Swipe starts only from the grabber or the header, so scrolling the body never closes the sheet
  • Controls at least 44 px high on phones

Content

  • The title and the buttons follow the rules of a dialog

Tokens

  • --overlay
  • --glass-fill-raised
  • --glass-highlight
  • --glass-ring-raised
  • --elevation-3
  • --radius-2xl
  • --radius-full
  • --surface-control
  • --space-2
  • --space-3
  • --space-4
  • --space-5
  • --border-rule
  • --duration-fast
  • --duration-base
  • --duration-slow
  • --ease-soft
  • --ease-standard

JSON

{
  "title": "Sheet",
  "path": "/components/sheet",
  "url": "https://design.werklist.com/components/sheet",
  "text": "https://design.werklist.com/agent/components/sheet",
  "summary": "A sheet is a dialog that enters from the bottom of a phone or the right edge of a wide window.",
  "version": "2.1.2",
  "kind": "component",
  "name": "Sheet",
  "purpose": "A dialog that enters from an edge: from the bottom on phones, from the right on wide screens.",
  "use": [
    "The phone form of a dialog, below 640 px",
    "A popover that holds a form, on a touch screen below 640 px",
    "Details or settings read beside the page, as a side sheet from the right",
    "A long list tied to the page, such as members or history"
  ],
  "avoid": [
    "A task that needs full attention on a wide screen: use a dialog",
    "A side sheet from the left edge: that edge belongs to navigation",
    "A second sheet over the first: close it first",
    "A task in several steps with a Back button: make it a page"
  ],
  "anatomy": [
    {
      "part": "Frame, bottom",
      "measure": "full width. Height fits the content, up to 92 percent of the window"
    },
    {
      "part": "Frame, bottom, full height",
      "measure": "the whole window inside the top and bottom safe areas. For large dialogs and forms longer than the window"
    },
    {
      "part": "Top corners, bottom sheet",
      "measure": "16 px",
      "token": "--radius-2xl"
    },
    {
      "part": "Grabber",
      "measure": "40 by 4 px, centered, 10 px from the top edge. Only when the sheet can be swiped",
      "token": "--surface-control, --radius-full"
    },
    {
      "part": "Bottom padding",
      "measure": "the bottom safe area, at least 12 px",
      "token": "--space-3"
    },
    {
      "part": "Frame, side",
      "measure": "right edge, full window height, 448 px wide. Full width below 640 px"
    },
    {
      "part": "Header, body, footer",
      "measure": "as in a dialog. The close button is 44 px on phones",
      "token": "--space-4, --space-5"
    },
    {
      "part": "Surface",
      "measure": "raised fill and shadow, near opaque. In the dark theme a 1 px top highlight and a 0.5 px ring",
      "token": "--glass-fill-raised, --elevation-3, --glass-highlight, --glass-ring-raised"
    },
    {
      "part": "Scrim",
      "measure": "no blur",
      "token": "--overlay"
    }
  ],
  "variants": [
    {
      "name": "Bottom",
      "use": "Below 640 px, the form every dialog takes. Swipe to close"
    },
    {
      "name": "Bottom, full height",
      "use": "Large dialogs and forms longer than the window"
    },
    {
      "name": "Side",
      "use": "From the right on wide screens, for details and settings read beside the page"
    }
  ],
  "sizes": [
    {
      "name": "Side, 448 px wide"
    },
    {
      "name": "Bottom, content height up to 92 percent of the window"
    },
    {
      "name": "Bottom, full height inside the safe areas"
    }
  ],
  "states": [
    {
      "name": "Opening, bottom",
      "change": "Rises from below the window edge over 300 ms",
      "tokens": [
        "--duration-slow",
        "--ease-soft"
      ]
    },
    {
      "name": "Closing, bottom",
      "change": "Falls back below the edge over 200 ms",
      "tokens": [
        "--duration-base",
        "--ease-standard"
      ]
    },
    {
      "name": "Opening, side",
      "change": "Fades in while traveling 40 px from the right, over 200 ms",
      "tokens": [
        "--duration-base",
        "--ease-soft"
      ]
    },
    {
      "name": "Closing, side",
      "change": "Reverse, over 150 ms",
      "tokens": [
        "--duration-fast",
        "--ease-standard"
      ]
    },
    {
      "name": "Dragging",
      "change": "Follows the finger downward at 70 percent of its travel. Upward travel does nothing. Released short of 90 px and slower than 500 px per second, it springs back"
    },
    {
      "name": "Unsaved entries",
      "change": "A swipe springs back and asks Discard the changes? first"
    },
    {
      "name": "Reduced motion",
      "change": "A fade only",
      "tokens": [
        "--duration-fast"
      ]
    },
    {
      "name": "Scrolled",
      "change": "As in a dialog: the 1 px line under the header or above the footer while content is hidden behind it",
      "tokens": [
        "--border-rule"
      ]
    }
  ],
  "keys": [
    {
      "key": "Tab, Shift+Tab",
      "action": "Move within the sheet, wrapping"
    },
    {
      "key": "Escape",
      "action": "Closes the sheet, or asks first when entries are unsaved"
    },
    {
      "key": "Enter in a single line field",
      "action": "Submits the form"
    }
  ],
  "accessibility": [
    "As a dialog: role dialog, aria-modal true, labeled by its title, focus kept inside and returned on close, the page behind out of reach",
    "The grabber is aria-hidden. The close button, named Close, does the same job",
    "Swipe starts only from the grabber or the header, so scrolling the body never closes the sheet",
    "Controls at least 44 px high on phones"
  ],
  "content": [
    "The title and the buttons follow the rules of a dialog"
  ],
  "tokens": [
    "--overlay",
    "--glass-fill-raised",
    "--glass-highlight",
    "--glass-ring-raised",
    "--elevation-3",
    "--radius-2xl",
    "--radius-full",
    "--surface-control",
    "--space-2",
    "--space-3",
    "--space-4",
    "--space-5",
    "--border-rule",
    "--duration-fast",
    "--duration-base",
    "--duration-slow",
    "--ease-soft",
    "--ease-standard"
  ]
}

Raw file: https://design.werklist.com/agent/components/sheet