Bottom Sheet
The BottomSheet component provides a modal bottom sheet that slides up from the bottom of the screen. It supports gesture controls for dragging, smooth animations, and customizable backdrop interactions.
Import
Section titled “Import”import { BottomSheet } from '@space-uy/pulsar-ui';Basic usage
Section titled “Basic usage”import { useRef } from 'react';
function MyComponent() {  const bottomSheetRef = useRef<BottomSheet>(null);
  const showBottomSheet = () => {    bottomSheetRef.current?.show();  };
  const hideBottomSheet = () => {    bottomSheetRef.current?.hide();  };
  return (    <>      <Button text="Show Bottom Sheet" onPress={showBottomSheet} />
      <BottomSheet ref={bottomSheetRef}>        <View style={{ padding: 20 }}>          <Text variant="h3">Bottom Sheet Content</Text>          <Text variant="pm">This is the content inside the bottom sheet.</Text>          <Button text="Close" onPress={hideBottomSheet} />        </View>      </BottomSheet>    </>  );}Properties
Section titled “Properties”| Property | Type | Required | Default value | Description | 
|---|---|---|---|---|
| children | React.ReactNode | ✅ | - | Content to display inside the bottom sheet | 
| onDismiss | () => void | ❌ | - | Callback when bottom sheet is dismissed | 
| style | StyleProp<ViewStyle> | ❌ | - | Custom styles for the bottom sheet container | 
Methods
Section titled “Methods”When using a ref, the following methods are available:
| Method | Type | Description | 
|---|---|---|
| show | () => void | Show the bottom sheet | 
| hide | () => void | Hide the bottom sheet | 
Basic examples
Section titled “Basic examples”Simple bottom sheet
Section titled “Simple bottom sheet”const bottomSheetRef = useRef<BottomSheet>(null);
<>  <Button text="Open Menu" onPress={() => bottomSheetRef.current?.show()} />
  <BottomSheet ref={bottomSheetRef}>    <View style={{ padding: 20, gap: 16 }}>      <Text variant="h3">Menu Options</Text>      <Button text="Option 1" variant="outline" />      <Button text="Option 2" variant="outline" />      <Button text="Option 3" variant="outline" />    </View>  </BottomSheet></>;Action sheet
Section titled “Action sheet”const actionSheetRef = useRef<BottomSheet>(null);
const handleAction = (action: string) => {  console.log('Action:', action);  actionSheetRef.current?.hide();};
<>  <Button text="Show Actions" onPress={() => actionSheetRef.current?.show()} />
  <BottomSheet ref={actionSheetRef}>    <View style={{ padding: 20 }}>      <Text variant="h4" style={{ marginBottom: 16, textAlign: 'center' }}>        Choose an action      </Text>      <View style={{ gap: 12 }}>        <Button          text="Edit"          variant="outline"          onPress={() => handleAction('edit')}        />        <Button          text="Share"          variant="outline"          onPress={() => handleAction('share')}        />        <Button          text="Delete"          variant="destructive"          onPress={() => handleAction('delete')}        />        <Button          text="Cancel"          variant="outline"          onPress={() => actionSheetRef.current?.hide()}        />      </View>    </View>  </BottomSheet></>;Advanced examples
Section titled “Advanced examples”Settings bottom sheet
Section titled “Settings bottom sheet”const settingsSheetRef = useRef<BottomSheet>(null);const [settings, setSettings] = useState({  notifications: true,  darkMode: false,  autoBackup: true,});
<>  <Button text="Settings" onPress={() => settingsSheetRef.current?.show()} />
  <BottomSheet ref={settingsSheetRef}>    <View style={{ padding: 20 }}>      <Text variant="h3" style={{ marginBottom: 20 }}>        Settings      </Text>
      <View style={{ gap: 16 }}>        <View          style={{            flexDirection: 'row',            justifyContent: 'space-between',            alignItems: 'center',          }}        >          <Text variant="pm">Notifications</Text>          <Switch            value={settings.notifications}            onValueChange={(value) =>              setSettings((prev) => ({ ...prev, notifications: value }))            }          />        </View>
        <View          style={{            flexDirection: 'row',            justifyContent: 'space-between',            alignItems: 'center',          }}        >          <Text variant="pm">Dark Mode</Text>          <Switch            value={settings.darkMode}            onValueChange={(value) =>              setSettings((prev) => ({ ...prev, darkMode: value }))            }          />        </View>
        <View          style={{            flexDirection: 'row',            justifyContent: 'space-between',            alignItems: 'center',          }}        >          <Text variant="pm">Auto Backup</Text>          <Switch            value={settings.autoBackup}            onValueChange={(value) =>              setSettings((prev) => ({ ...prev, autoBackup: value }))            }          />        </View>      </View>
      <Button        text="Done"        onPress={() => settingsSheetRef.current?.hide()}        style={{ marginTop: 20 }}      />    </View>  </BottomSheet></>;Form bottom sheet
Section titled “Form bottom sheet”const formSheetRef = useRef<BottomSheet>(null);const [formData, setFormData] = useState({  name: '',  email: '',  message: '',});
const handleSubmit = () => {  console.log('Form submitted:', formData);  setFormData({ name: '', email: '', message: '' });  formSheetRef.current?.hide();};
<>  <Button text="Contact Us" onPress={() => formSheetRef.current?.show()} />
  <BottomSheet ref={formSheetRef}>    <View style={{ padding: 20 }}>      <Text variant="h3" style={{ marginBottom: 20 }}>        Contact Form      </Text>
      <View style={{ gap: 16 }}>        <Input          label="Name"          value={formData.name}          onChangeText={(name) => setFormData((prev) => ({ ...prev, name }))}          placeholder="Your name"        />
        <Input          label="Email"          value={formData.email}          onChangeText={(email) => setFormData((prev) => ({ ...prev, email }))}          placeholder="your@email.com"          keyboardType="email-address"        />
        <TextArea          label="Message"          value={formData.message}          onChangeText={(message) =>            setFormData((prev) => ({ ...prev, message }))          }          placeholder="Your message..."          numberOfLines={4}        />
        <View style={{ flexDirection: 'row', gap: 12 }}>          <Button            text="Cancel"            variant="outline"            style={{ flex: 1 }}            onPress={() => formSheetRef.current?.hide()}          />          <Button text="Submit" style={{ flex: 1 }} onPress={handleSubmit} />        </View>      </View>    </View>  </BottomSheet></>;Selection bottom sheet
Section titled “Selection bottom sheet”const selectionSheetRef = useRef<BottomSheet>(null);const [selectedOption, setSelectedOption] = useState<string | null>(null);
const options = [  { id: 'option1', title: 'Option 1', description: 'First option description' },  {    id: 'option2',    title: 'Option 2',    description: 'Second option description',  },  { id: 'option3', title: 'Option 3', description: 'Third option description' },];
const handleSelect = (optionId: string) => {  setSelectedOption(optionId);  selectionSheetRef.current?.hide();};
<>  <Button    text={      selectedOption        ? options.find((o) => o.id === selectedOption)?.title        : 'Select Option'    }    onPress={() => selectionSheetRef.current?.show()}  />
  <BottomSheet ref={selectionSheetRef}>    <View style={{ padding: 20 }}>      <Text variant="h3" style={{ marginBottom: 20 }}>        Choose Option      </Text>
      <View style={{ gap: 12 }}>        {options.map((option) => (          <Pressable            key={option.id}            style={{              padding: 16,              borderRadius: 8,              backgroundColor:                selectedOption === option.id                  ? colors.primary + '10'                  : colors.altBackground,              borderWidth: selectedOption === option.id ? 1 : 0,              borderColor: colors.primary,            }}            onPress={() => handleSelect(option.id)}          >            <View              style={{                flexDirection: 'row',                justifyContent: 'space-between',                alignItems: 'center',              }}            >              <View style={{ flex: 1 }}>                <Text variant="pm">{option.title}</Text>                <Text variant="ps" style={{ opacity: 0.7, marginTop: 4 }}>                  {option.description}                </Text>              </View>              {selectedOption === option.id && (                <Icon name="Check" size={20} color={colors.primary} />              )}            </View>          </Pressable>        ))}      </View>    </View>  </BottomSheet></>;Implementation notes
Section titled “Implementation notes”- Uses React Native Reanimated for smooth animations and gesture handling
- Supports swipe down gesture to dismiss the bottom sheet
- Backdrop can be tapped to dismiss the sheet
- Automatically handles safe area insets for devices with notches
- The bottom sheet slides up from the bottom with a smooth animation
- Content is scrollable if it exceeds the available space
- Keyboard automatically adjusts the bottom sheet height when inputs are focused
Animation details
Section titled “Animation details”- Slide up animation: 300ms duration with smooth easing curve
- Gesture handling: Real-time dragging with spring animation on release
- Backdrop fade: Synchronized with slide animation for smooth effect
- Keyboard avoidance: Automatic adjustment when virtual keyboard appears
Accessibility
Section titled “Accessibility”- Bottom sheet properly traps focus when shown
- Screen readers announce when the bottom sheet appears and disappears
- Gesture controls are accessible via assistive technologies
- Backdrop dismissal is properly announced to screen readers
- All interactive elements within maintain proper accessibility labels
Performance
Section titled “Performance”- Animations run on the UI thread for 60 FPS performance
- Content is only rendered when the bottom sheet is visible
- Efficient gesture handling without blocking the main thread
- Memory efficient implementation with proper cleanup