Pular para o conteúdo

Sheets

components specs/components/sheets.kmd

Surface anchored to an edge of the screen, slidable to reveal secondary content — bottom sheets (mobile primary) and side sheets (tablet/desktop). Material parity (`/components/bottom-sheets` and `/components/side-sheets`). Covers modal vs standard, drag gestures, scrim, focus trap, and snap points.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Spec — Sheets

Facet Visual of Koder Design. Material parity: https://m3.material.io/components/bottom-sheets and https://m3.material.io/components/side-sheets.

2 anchor positions × 2 modalities

AnchorModalityMobileTablet/Desktop
Bottom — modalBlocks page✓ PrimaryUse dialog instead
Bottom — standardInline, page still interactive✓ SecondaryRare
Side — modalBlocks page✓ Primary
Side — standardInline, page still interactive✓ Common (3-pane layout)

Pick anchor by surface: bottom on mobile (thumb-reach); side on tablet/desktop (wider real estate).

Anatomy (bottom sheet, modal)

                  ▼   scrim (40% black overlay)

     ┌──────────────────────────────────────┐
     │            ━━━                       │ ← drag handle
     │  Sheet title                         │
     │  ──────────────────────────────────  │
     │  Content row 1                       │
     │  Content row 2                       │
     │  Content row 3                       │
     │                                      │
     │  [Confirm]                  [Cancel] │
     └──────────────────────────────────────┘
            (anchored to bottom edge)
  • Top corner radius: 28 px (bottom corners flush with screen)
  • Drag handle: 4 px × 32 px pill, centered, 22 px from top
  • Container bg: surface-container-low
  • Elevation: 1 dp (modal scrim does the visual lift)
  • Padding: 24 px horizontal, 16 px vertical
  • Min height: 50% of viewport (default open state)
  • Max height: 90% of viewport (leaves room to dismiss by tap above)

Anatomy (side sheet, standard)

┌────────────────────────┬───────────────────┐
│                        │ Sheet title       │
│   Main content         │ ────────────────  │
│                        │ Detail content    │
│                        │                   │
│                        │                   │
└────────────────────────┴───────────────────┘
                          ←── 320-400 dp ──→
  • Width: 320-400 dp (fixed; not draggable in width)
  • Anchor: right edge (default; left for RTL or 3-pane layouts)
  • Border: 1 px outline-variant on inner edge
  • Container bg: surface-container-low
  • No corner radius on the screen-edge corners

R1 — Modality

ModalityScrimFocus trapDismiss
ModalYes (40% black)YesScrim tap + drag-down + Esc + close ×
StandardNoNoClose × button only OR programmatic

Modal sheet behaves like a dialog with bottom/side anchor: blocks page until dismissed. Standard sheet stays open and lets user interact with the rest of the page.

R2 — Bottom sheet snap points

SnapHeightUse
Closed0 px (hidden)Initial state
Peek25% viewport OR ~120 pxOptional teaser visible
Half50% viewportDefault open
Expanded90% viewportUser dragged up
Full100% viewportBecomes full-screen sheet

Drag handle moves between snap points; velocity > 500 px/s expands or collapses past midpoint.

Peek snap is OPTIONAL — most sheets have only Closed → Half → Expanded.

R3 — Side sheet snap points

SnapWidthUse
Closed0 px (hidden)Initial state
Open320-400 dpDefault
Wide50% viewportUser expanded (rare)

Side sheets snap at fixed widths; don't free-resize like a window pane. If user needs free resize, use a resizable pane layout (not sheet).

R4 — Drag gesture

  • Bottom sheet: drag handle bar OR anywhere in the sheet header
    • Drag down: collapse to next snap (Expanded → Half → Closed)
    • Drag up: expand to next snap (Half → Expanded)
    • Velocity-based: fast flick passes through all snaps
  • Side sheet: usually NO drag gesture (button-controlled)
  • Content inside sheet scrolls independently — drag must originate on handle or header, not on scrolling content

Disabled when:

  • Reduced motion preference active (still snaps, no smooth follow)
  • Sheet is in Expanded state and content is mid-scroll

R5 — Scrim

Modal sheets ONLY. Scrim is 40% opacity black overlay covering the rest of the screen. Tap scrim → dismiss sheet.

Side sheet (modal) scrim covers the main content area, NOT the side sheet itself.

Standard sheets have NO scrim — user can interact with the page around the sheet.

R6 — Focus trap and keyboard

Modal sheets trap focus when open:

  • Tab cycles within sheet, never escapes
  • Esc dismisses (calls onDismiss callback)
  • On open: focus moves to first focusable element OR sheet title (if labelled)
  • On close: focus returns to the trigger element

Standard sheets do NOT trap focus — Tab moves naturally between sheet and page.

R7 — Mobile keyboard interaction

When mobile soft keyboard opens while a bottom sheet is showing:

  • Sheet animates up to remain visible above keyboard
  • Sheet snap point becomes "above keyboard" until keyboard closes
  • Don't shrink sheet content height; let it scroll

R8 — Animation

  • Open: slide-in from edge (motion-medium, ~250 ms) + scrim fade-in (modal only)
  • Close: slide-out (motion-medium) + scrim fade-out
  • Snap transition: spring animation (Material 3 emphasized decelerate, ~350 ms)
  • Drag follow: 1:1 with finger; no spring during drag
  • Reduced motion: instant in/out; no spring; no drag-follow easing (still works, just snaps)

R9 — Accessibility

  • Modal sheet: role="dialog" + aria-modal="true" + aria-labelledby pointing to sheet title
  • Standard sheet: role="complementary" + aria-label describing the sheet's purpose
  • Drag handle: role="button" + aria-label="Drag to resize" + keyboard support (Arrow Up/Down to expand/collapse)
  • Dismiss button: aria-label="Close sheet"
  • Screen reader announces sheet on open: "Settings, dialog"

R10 — Per-preset variation

PresetBottom sheetSide sheet
material328 px top corners, drag handleFlush edges, no handle
material216 px top corners, no handleFlush, 4 dp shadow
ios_cupertino16 px top corners, swipe-down to dismissInspector-style overlay
gnomeAdwaita BottomBar style, no handle (button-controlled)Sidebar embedded in window
windows_11Mica backdrop, system-style close ×Acrylic sidebar
brutalistSharp top corners, 4 px thick top borderSharp edges, thick border
terminal_classicASCII box at bottom of screenVertical pane via tmux-style split

R11 — Density

Inherits surface density from customization.kmd. Bottom sheet default padding 24 px / 16 px → compact 16 px / 12 px → comfortable 32 px / 20 px.

R12 — Forbidden patterns

  • ❌ Stacking sheets (sheet over sheet — use single sheet with navigation inside, or break into separate flows)
  • ❌ Bottom sheet on Expanded/Large window-size class (use side sheet or dialog)
  • ❌ Side sheet narrower than 320 dp (cramped)
  • ❌ Side sheet wider than 50% of viewport (defeats sheet semantics; switch to full-page or dialog)
  • ❌ Standard sheet with scrim (contradicts "non-modal")
  • ❌ Modal sheet without focus trap
  • ❌ Drag-to-resize that loses content position (scroll state must survive snap changes)
  • ❌ Dismiss-by-scroll-content (content scroll triggers sheet dismissal — confusing; only handle drag dismisses)
  • ❌ Bottom sheet without bottom safe-area inset
  • app-layout/safe-area.kmd — bottom sheet bottom inset
  • app-layout/window-size-classes.kmd — when to choose bottom vs side
  • themes/elevation.kmd — modal scrim role
  • themes/color-roles.kmdsurface-container-low token
  • interaction/states.kmd — handle hover/press
  • components/dialogs.kmd — sibling modal pattern (centered vs edge-anchored)
  • foundations/elements.kmd — Container family

Referências