# Build a Bottom Sheet Component for Mobile Web

Source: https://www.techinterview.org/post/3233475371/build-bottom-sheet-component-mobile-web/
Updated: 2026-06-30 · techinterview.org

"Build a bottom sheet" is a mobile-web frontend question that punishes shortcuts. Apple Maps, Apple Music, Slack iOS, Linear iOS all use bottom sheets as their primary modal pattern; on the web, the implementation is harder than it looks. This guide covers what interviewers probe for senior mobile-web roles.

## Clarify scope

- Single sheet height or multi-snap (collapsed, half, full)?

- Drag-to-dismiss?

- Backdrop dismiss?

- Content longer than sheet — internal scroll?

- iOS Safari vs Android Chrome — both?

## The CSS foundation

- Position fixed at bottom, off-screen by default

- Translate Y based on snap state

- Full-width on small screens; max-width on larger

- Backdrop is a sibling element with opacity transitioning


```
.sheet {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  transform: translateY(var(--sheet-y));
  transition: transform 200ms ease-out;
  /* During drag, transition is none; CSS variable updates inline */
}
```


## Snap points

- Define snap points in pixels or percentages of viewport

- e.g., [collapsed: 100px, half: 50%, full: 90%]

- Drag releases snap to nearest based on velocity + position

- State machine: current snap, target snap during drag

## The drag gesture

- `pointerdown` on the handle (or anywhere if drag-anywhere): record start Y, start sheet Y

- `pointermove`: update sheet Y; throttle with rAF

- `pointerup`: compute velocity over last few moves; snap to closest snap point factoring velocity

Use pointer events with `setPointerCapture` for robustness across browsers.

## The internal scroll wrinkle

The hard problem. The sheet has scrollable content. The user can:

- Drag the handle to resize the sheet

- Scroll the content within the sheet

- If the content is at scrollTop = 0 and they drag down, should the sheet shrink or the page scroll?

The standard pattern: while scrollTop > 0, scroll the content. While scrollTop = 0 and the gesture is downward, drag the sheet. This requires careful gesture-handler coordination.

## iOS Safari quirks

- iOS Safari uses momentum scrolling (overscroll-behavior). Disable on the sheet container or it interferes with drag

- The viewport bottom 88px is reserved for browser chrome on iOS — bottom sheets must respect this

- iOS bounce can fight your drag; `touch-action: pan-y; overscroll-behavior: contain` on the sheet body

- Safe-area-inset-bottom for the home indicator

## The keyboard challenge

If the sheet has an input, the keyboard pushes content. Strategies:

- Listen to the visualViewport API for keyboard show/hide

- Resize the sheet to occupy what is left after the keyboard

- Snap the sheet to "full" automatically when keyboard opens

VirtualViewport is supported on iOS Safari 13+ and Android Chrome.

## Backdrop and dismissal

- Backdrop fades from 0 to 0.4 opacity as sheet rises

- Tap backdrop dismisses

- Drag down past collapsed snap dismisses

- Hardware back on Android dismisses (history.back integration)

## Accessibility

- Sheet is `role="dialog"` with `aria-modal="true"` when open

- Focus trap inside the sheet

- Focus returns to trigger on dismiss

- Escape key dismisses on desktop

- Drag handle has `role="separator"` with `aria-valuenow` if you support keyboard resize

## Mobile-only or also desktop?

- Some teams use bottom sheet only on mobile; modal on desktop

- Others (Vercel, Linear) ship the same component on both

- If responsive: max-width and centering on larger viewports turn it into a centered modal

## Performance

- Use transform, not top/bottom — GPU-accelerated

- Throttle pointermove with rAF

- Avoid re-renders during drag; manipulate the DOM directly via ref + CSS variables

- Lazy-render content inside the sheet only when opened

## Edge cases interviewers love

- User flicks the sheet quickly — velocity-based snap

- User drags past max snap — rubber-band resistance, do not exceed

- Content at scrollTop = 0 + drag down should resize; at scrollTop > 0 should scroll

- Keyboard opens mid-drag — handle the visualViewport event

- Background audio playback context interrupts the gesture

## What separates senior from staff

Senior implementations handle drag, snap, and dismiss. Staff implementations handle the internal-scroll vs sheet-drag coordination, the keyboard-aware sizing, and the safe-area inset on iOS. Principal-level discussion includes the responsive behavior across web and embedded webview contexts.

## Frequently Asked Questions

### Library options for production?

Vaul (by Emil Kowalski) is the modern React default. Mantine has one. Older: react-spring-bottom-sheet. Vaul handles most edge cases including the scroll-coordination problem.

### How do I handle a bottom sheet inside a PWA standalone mode?

Standalone PWA loses the browser chrome but the iOS home indicator is still there. Respect `env(safe-area-inset-bottom)`. Test on actual device in standalone mode; simulator misses the home indicator interactions.

### What about portrait vs landscape?

Bottom sheets work in portrait; in landscape on phones, they cover too much. Switch to side sheet or modal in landscape.
