Skip to main content

Overview

The Bottom Sheet component slides up from the bottom of the viewport, presenting contextual actions, modal forms, sharing trays, or filter menus optimized for mobile thumb navigation. It includes native touch swipe gestures, mouse drag support, backdrop dismissal, keyboard accessibility (Escape to dismiss), and smooth spring-like animations.

Unlike standard desktop modals, bottom sheets preserve user flow on mobile devices by staying anchored to the bottom edge and offering intuitive swipe-down dismiss gestures.

<!-- Trigger button -->
<button type="button" class="btn btn-primary" data-boodoo-toggle="bottom-sheet" data-boodoo-target="#demoSheet">
  <span>Open Bottom Sheet</span>
</button>

<!-- Bottom Sheet structure -->
<div class="bottom-sheet" id="demoSheet" tabindex="-1" role="dialog" aria-labelledby="sheetTitle" aria-modal="true">
  <div class="bottom-sheet-handle-wrap" aria-hidden="true">
    <div class="bottom-sheet-handle"></div>
  </div>
  <div class="bottom-sheet-header">
    <h5 class="bottom-sheet-title" id="sheetTitle"><span>Quick Actions</span></h5>
    <button type="button" class="btn-close" data-boodoo-dismiss="bottom-sheet" aria-label="Close"></button>
  </div>
  <div class="bottom-sheet-body">
    <p>Sheet content with touch gestures and drag-to-dismiss.</p>
  </div>
  <div class="bottom-sheet-footer">
    <button type="button" class="btn btn-secondary" data-boodoo-dismiss="bottom-sheet">
      <span>Cancel</span>
    </button>
    <button type="button" class="btn btn-primary" data-boodoo-dismiss="bottom-sheet">
      <span>Confirm</span>
    </button>
  </div>
</div>

Anatomy & Sizing Variants

Bottom sheets support three distinct height sizing presets to accommodate different mobile content densities:

Class Name Max Height Ideal Use Case
.bottom-sheet 90vh (default) General-purpose actions, detailed forms, and long content lists with internal scroll.
.bottom-sheet-half 50vh Compact action sheets, share menus, confirmation dialogs, and filter trays.
.bottom-sheet-full 100vh Full-screen mobile workflows, checkout wizards, and modal detail views.

Touch & Drag Gestures

Boodoo's Bottom Sheet includes built-in touch and mouse gesture recognition with zero external dependencies:

  • Handle Dragging: The .bottom-sheet-handle-wrap bar provides an accessible grab target. Dragging down smoothly translates the sheet in real time.
  • Swipe-to-Dismiss Threshold: Dragging beyond 120 pixels downwards automatically triggers dismissal with a fluid slide-down exit animation.
  • Snap-Back Elasticity: Releasing the drag before the threshold smoothly snaps the sheet back to its fully expanded position.
  • Backdrop Click: Tapping or clicking outside the sheet dismisses the sheet and frees body scroll lock.

Accessibility & Keyboard Interactions

Boodoo ensures mobile overlay components meet WCAG 2.2 AA accessibility guidelines:

  • Keyboard Dismissal: Pressing the Escape key immediately closes the active bottom sheet and cleans up document event listeners.
  • ARIA Dialog Semantics: Apply role="dialog" and aria-modal="true" to the root .bottom-sheet element. Label the dialog using aria-labelledby="[id-of-title]".
  • Body Scroll Lock: While the bottom sheet is open, overflow: hidden is applied to document.body to prevent background scrolling underneath the sheet.
  • Handle Wrap ARIA: The drag handle is marked aria-hidden="true" so screen readers focus directly on meaningful titles and actionable controls.

JavaScript API & Lifecycle

Manage Bottom Sheet instances programmatically via vanilla JavaScript:

import { BottomSheet } from 'boodoo';

// Retrieve or create an instance
const sheetEl = document.getElementById('demoSheet');
const sheet = BottomSheet.getOrCreateInstance(sheetEl);

// Programmatic control
sheet.show();
sheet.hide();
sheet.toggle();

// Full lifecycle teardown (detaches gesture listeners & removes backdrop)
sheet.dispose();

Lifecycle Events

Custom events allow you to hook into bottom sheet state transitions:

Event Type Description
boodoo.show.bottomSheet Fires immediately when show() is called. Cancellable via event.preventDefault().
boodoo.shown.bottomSheet Fires when the sheet has completed its slide-up transition and backdrop is visible.
boodoo.hide.bottomSheet Fires immediately when hide() is called. Cancellable via event.preventDefault().
boodoo.hidden.bottomSheet Fires when the sheet has fully slid off-screen and the backdrop is removed from the DOM.

Multi-Framework Integration

Integrate Bottom Sheet across your favorite frontend stacks:

React Integration

import React from 'react';
import { useDisclosure } from 'boodoo/react';

export function QuickShareSheet() {
  const { isOpen, onOpen, onClose } = useDisclosure();

  return (
    <>
      <button type="button" className="btn btn-primary" onClick={onOpen}>
        <span>Share Document</span>
      </button>

      {isOpen && (
        <div className="bottom-sheet show" role="dialog" aria-modal="true">
          <div className="bottom-sheet-handle-wrap">
            <div className="bottom-sheet-handle"></div>
          </div>
          <div className="bottom-sheet-header">
            <h5 className="bottom-sheet-title"><span>Share Options</span></h5>
            <button type="button" className="btn-close" onClick={onClose} aria-label="Close"></button>
          </div>
          <div className="bottom-sheet-body">
            <p>Copy shareable link or export as PDF.</p>
          </div>
        </div>
      )}
    </>
  );
}

Vue 3 Integration

<script setup>
import { useDisclosure } from 'boodoo/vue';

const { isOpen, open, close } = useDisclosure();
</script>

<template>
  <button type="button" class="btn btn-primary" @click="open">
    <span>Open Actions</span>
  </button>

  <div v-if="isOpen" class="bottom-sheet show" role="dialog" aria-modal="true">
    <div class="bottom-sheet-handle-wrap"><div class="bottom-sheet-handle"></div></div>
    <div class="bottom-sheet-header">
      <h5 class="bottom-sheet-title"><span>Actions</span></h5>
      <button type="button" class="btn-close" @click="close" aria-label="Close"></button>
    </div>
    <div class="bottom-sheet-body">
      <p>Perform rapid batch tasks on mobile.</p>
    </div>
  </div>
</template>

React Native Mobile

For native iOS and Android apps, use Boodoo's pure primitive surfaces without DOM dependencies:

import React, { useState } from 'react';
import { View, Text, StyleSheet, Modal, Pressable } from 'react-native';
import { Button } from 'boodoo/react-native';

export function MobileBottomSheet() {
  const [visible, setVisible] = useState(false);

  return (
    <View style={styles.container}>
      <Button variant="primary" label="Open Native Sheet" onPress={() => setVisible(true)} />
      <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
        <Pressable style={styles.backdrop} onPress={() => setVisible(false)}>
          <View style={styles.sheet}>
            <View style={styles.handle} />
            <Text style={styles.title}>Native Action Sheet</Text>
            <Text style={styles.body}>Rendered with zero DOM dependencies.</Text>
          </View>
        </Pressable>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  backdrop: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' },
  sheet: { backgroundColor: '#ffffff', borderTopLeftRadius: 20, borderTopRightRadius: 20, padding: 20 },
  handle: { width: 40, height: 4, backgroundColor: '#cbd5e1', borderRadius: 2, alignSelf: 'center', marginBottom: 16 },
  title: { fontSize: 18, fontWeight: '600', marginBottom: 8 },
  body: { fontSize: 14, color: '#64748b' }
});

Explore other contextual overlays and dialog systems in Boodoo:

  • Modal: Centered dialogs and lightboxes for desktop and tablet screens.
  • Offcanvas: Full-height sliding sidebars from left, right, top, or bottom edges.
  • Speed Dial: Floating mobile action buttons with expanding quick-action menus.
Quick Share & Actions

Swipe down on the handle or drag anywhere to dismiss this bottom sheet.

Filter Results

This is a half-height sheet variant using .bottom-sheet-half, ideal for quick option selections.

Full-Height Sheet

This is a full-height sheet variant using .bottom-sheet-full, covering the full viewport on mobile for immersive task flows.