Bottom Sheet

Material Design 3 bottom sheet component for mobile-first interactions

Bottom Sheet

Bottom sheets are surfaces containing supplementary content that are anchored to the bottom of the screen. They’re commonly used on mobile devices for contextual actions, settings, or additional information.

Basic Usage

Basic Bottom Sheet

Bottom Sheet Title

Bottom sheet content goes here.

Modal bottom sheets require user interaction before allowing interaction with the rest of the screen. They include a backdrop that prevents interaction with underlying content.

Modal Bottom Sheet

Modal Bottom Sheet

This is a modal bottom sheet. Users must interact with it or dismiss it before continuing.

Persistent (Non-Modal) Bottom Sheet

Persistent bottom sheets remain visible while allowing interaction with the main content. They don’t have a backdrop.

Persistent Bottom Sheet

Persistent Bottom Sheet

This sheet stays open while you interact with the page.

Sizes

Bottom sheets come in multiple height variants to accommodate different content needs.

Small (40vh)

Small Bottom Sheet

Small bottom sheet - 40% of viewport height

Medium (60vh)

Medium Bottom Sheet

Medium bottom sheet - 60% of viewport height

Large (80vh)

Large Bottom Sheet

Large bottom sheet - 80% of viewport height

Full Height (100vh)

Full Height Bottom Sheet

Full height bottom sheet - Takes entire viewport

Handle Options

With Handle (Default)

The drag handle provides a visual indicator that the sheet can be dismissed by dragging.

Bottom Sheet with Handle

Bottom sheet with drag handle

Without Handle

Remove the handle for a cleaner look when drag-to-dismiss isn’t needed.

Bottom Sheet without Handle

No Handle

Bottom sheet without drag handle

Surface Variants

Bottom sheets support different surface color variants from the Material Design 3 color system.

Surface Container Low (Default)

Surface Container Low

Default surface container low background

Surface

Surface Variant

Surface background variant

Surface Container

Surface Container Variant

Surface container background variant

Surface Container High

Surface Container High Variant

Surface container high background variant

Content Options

Scrollable Content

For long content that needs scrolling within the bottom sheet body.

Scrollable Content

Scrollable Content

Long content that scrolls...

More content...

Even more content...

No Padding

Remove default padding from the body for custom layouts.

No Padding

Full width image

Content with custom padding

Compact Spacing

Reduce padding for more compact layouts.

Compact Spacing

Compact Sheet

Reduced padding for compact display

Partial Open State

Bottom sheets can be partially opened to show a preview of content.

Partial Open State

This sheet opens partially at first

Drag up or tap to fully open

Common Use Cases

Action Sheet

Display a list of actions for the user to choose from.

Action Sheet

Choose an action

Form Sheet

Use for forms and data input.

Form Sheet

Add New Item

Filter Sheet

Display filtering options.

Filter Sheet

Filters

Category

Price Range

JavaScript Control

Basic JavaScript for controlling bottom sheet visibility:

JavaScript Control

Bottom Sheet

Content here

Best Practices

Mobile-First Design

Bottom sheets are designed primarily for mobile interfaces:

  • Use them on smaller screens (typically < 768px)
  • Consider using modals or dialogs on desktop
  • Ensure touch targets are at least 44×44px
  • Test on actual mobile devices

Accessibility

  • Use semantic HTML elements
  • Include proper ARIA labels for close buttons
  • Ensure keyboard navigation works correctly
  • Provide clear focus indicators
  • Don’t trap focus when using persistent sheets

Accessible Bottom Sheet

Content Guidelines

  • Keep content focused and concise
  • Use clear, actionable button labels
  • Provide a way to dismiss (handle, close button, or backdrop tap)
  • Don’t nest multiple bottom sheets
  • Consider using partial open for previews

Performance

  • Use CSS transitions for smooth animations
  • Avoid heavy content in bottom sheets
  • Lazy load content when possible
  • Use will-change sparingly for animations

API Reference

Class Names

ClassDescription
.bottom-sheetBase bottom sheet container (required)
.bottom-sheet-backdropBackdrop overlay for modal sheets
.bottom-sheet-backdrop-showShow the backdrop
.bottom-sheet-showShow the bottom sheet
.bottom-sheet-handleDrag handle indicator
.bottom-sheet-headerHeader section with title and close button
.bottom-sheet-titleTitle text in header
.bottom-sheet-bodyMain content area
.bottom-sheet-footerFooter section for actions
.bottom-sheet-closeClose button styling

Size Modifiers

ClassDescription
.bottom-sheet-smSmall height (40vh)
.bottom-sheet-mdMedium height (60vh)
.bottom-sheet-lgLarge height (80vh)
.bottom-sheet-fullFull height (100vh)

Type Modifiers

ClassDescription
.bottom-sheet-modalModal bottom sheet (with backdrop)
.bottom-sheet-persistentPersistent bottom sheet (no backdrop)
.bottom-sheet-partialPartially opened state

Surface Modifiers

ClassDescription
.bottom-sheet-surfaceSurface background color
.bottom-sheet-surface-containerSurface container background
.bottom-sheet-surface-container-highSurface container high background

Content Modifiers

ClassDescription
.bottom-sheet-no-handleHide the drag handle
.bottom-sheet-scrollableEnable scrolling with max-height
.bottom-sheet-no-paddingRemove body padding
.bottom-sheet-compactReduced padding throughout

Framework Examples

React

import { useState } from 'react';

interface BottomSheetProps {
  isOpen: boolean;
  onClose: () => void;
  title: string;
  children: React.ReactNode;
  size?: 'sm' | 'md' | 'lg' | 'full';
  modal?: boolean;
}

export function BottomSheet({
  isOpen,
  onClose,
  title,
  children,
  size = 'md',
  modal = true
}: BottomSheetProps) {
  return (
    <>
      {modal && (
        <div
          className={`bottom-sheet-backdrop ${isOpen ? 'bottom-sheet-backdrop-show' : ''}`}
          onClick={onClose}
        />
      )}
      <div className={`bottom-sheet bottom-sheet-${size} ${isOpen ? 'bottom-sheet-show' : ''}`}>
        <div className="bottom-sheet-handle" />
        <div className="bottom-sheet-header">
          <h2 className="bottom-sheet-title">{title}</h2>
          <button className="bottom-sheet-close" onClick={onClose} aria-label="Close">
            ×
          </button>
        </div>
        <div className="bottom-sheet-body">
          {children}
        </div>
      </div>
    </>
  );
}

// Usage
function App() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button className="btn btn-primary" onClick={() => setIsOpen(true)}>
        Open Sheet
      </button>
      <BottomSheet
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        title="My Bottom Sheet"
      >
        <p>Content goes here</p>
      </BottomSheet>
    </>
  );
}

Vue

<template>
  <div>
    <div
      v-if="modal"
      :class="['bottom-sheet-backdrop', { 'bottom-sheet-backdrop-show': isOpen }]"
      @click="$emit('close')"
    />
    <div :class="sheetClasses">
      <div class="bottom-sheet-handle" />
      <div class="bottom-sheet-header">
        <h2 class="bottom-sheet-title">{{ title }}</h2>
        <button class="bottom-sheet-close" @click="$emit('close')" aria-label="Close">
          ×
        </button>
      </div>
      <div class="bottom-sheet-body">
        <slot />
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({
  isOpen: Boolean,
  title: String,
  size: {
    type: String,
    default: 'md'
  },
  modal: {
    type: Boolean,
    default: true
  }
});

defineEmits(['close']);

const sheetClasses = computed(() => [
  'bottom-sheet',
  `bottom-sheet-${props.size}`,
  { 'bottom-sheet-show': props.isOpen }
]);
</script>

<!-- Usage -->
<template>
  <button class="btn btn-primary" @click="isOpen = true">
    Open Sheet
  </button>
  <BottomSheet
    :is-open="isOpen"
    @close="isOpen = false"
    title="My Bottom Sheet"
  >
    <p>Content goes here</p>
  </BottomSheet>
</template>

<script setup>
import { ref } from 'vue';
import BottomSheet from './BottomSheet.vue';

const isOpen = ref(false);
</script>
  • Dialog - Modal dialogs for desktop
  • Card - Container component
  • Button - Action buttons

See Also