BottomSheet 10.18.1
The bottom sheet component is a modified dialog that slides from the bottom of the screen, common pattern in mobile apps.
Bottom sheets can contain any anything so let your imagination fly. Based on Headless UI.
Anatomy
<BottomSheet> <BottomSheet.Panel> <BottomSheet.Draghandle> <BottomSheet.Title>...</BottomSheet.Title> </BottomSheet.Draghandle> ... </BottomSheet.Panel> <BottomSheet.Backdrop /> </BottomSheet>
Default
By default, the <Bottomsheet.Panel />
occupies a height of 33.33% (h-1/3
) of the viewport.
"use client";
import { useState } from "react";
import { BottomSheet, Button } from "@heathmont/moon-core-tw";
export const Default = () => {
const [isOpen, setIsOpen] = useState(false);
const closeBottomSheet = () => {
setIsOpen(false);
};
const openBottomSheet = () => {
setIsOpen(true);
};
return (
<>
<Button variant="outline" onClick={openBottomSheet}>
Default BottomSheet
</Button>
<BottomSheet open={isOpen} onClose={closeBottomSheet}>
<BottomSheet.Panel>
<div className="flex grow items-center justify-center bg-jiren text-piccolo">
BottomSheet content
</div>
</BottomSheet.Panel>
<BottomSheet.Backdrop />
</BottomSheet>
</>
);
};
export default Default;
Sizes
Adjust the default height of the bottom sheet panel by modifying the CSS height class of <BottomSheet.Panel>
"use client";
import { useState } from "react";
import { BottomSheet, Button } from "@heathmont/moon-core-tw";
export const Sizes = () => {
const [isSmallOpen, setIsSmallOpen] = useState(false);
const closeSmallBottomSheet = () => {
setIsSmallOpen(false);
};
const openSmallBottomSheet = () => {
setIsSmallOpen(true);
};
const [isMediumOpen, setIsMediumOpen] = useState(false);
const closeMediumBottomSheet = () => {
setIsMediumOpen(false);
};
const openMediumBottomSheet = () => {
setIsMediumOpen(true);
};
const [isFullOpen, setIsFullOpen] = useState(false);
const closeFullBottomSheet = () => {
setIsFullOpen(false);
};
const openFullBottomSheet = () => {
setIsFullOpen(true);
};
return (
<>
<div>
<Button variant="outline" onClick={openSmallBottomSheet}>
Default small BottomSheet
</Button>
<BottomSheet open={isSmallOpen} onClose={closeSmallBottomSheet}>
<BottomSheet.Panel className="flex flex-col gap-2 items-center">
<div className="flex grow w-full items-center justify-center bg-jiren text-piccolo">
BottomSheet content
</div>
<Button onClick={closeSmallBottomSheet}>Optional close</Button>
</BottomSheet.Panel>
<BottomSheet.Backdrop />
</BottomSheet>
</div>
<div>
<Button variant="outline" onClick={openMediumBottomSheet}>
Medium BottomSheet
</Button>
<BottomSheet open={isMediumOpen} onClose={closeMediumBottomSheet}>
<BottomSheet.Panel className="flex flex-col gap-2 items-center h-2/3">
<div className="flex grow w-full items-center justify-center bg-jiren text-piccolo">
BottomSheet content
</div>
<Button onClick={closeMediumBottomSheet}>Optional close</Button>
</BottomSheet.Panel>
<BottomSheet.Backdrop />
</BottomSheet>
</div>
<div>
<Button variant="outline" onClick={openFullBottomSheet}>
Fullscreen BottomSheet
</Button>
<BottomSheet open={isFullOpen} onClose={closeFullBottomSheet}>
<BottomSheet.Panel className="flex flex-col gap-2 items-center h-full">
<div className="flex grow w-full items-center justify-center bg-jiren text-piccolo">
BottomSheet content
</div>
<Button onClick={closeFullBottomSheet}>Optional close</Button>
</BottomSheet.Panel>
<BottomSheet.Backdrop />
</BottomSheet>
</div>
</>
);
};
export default Sizes;
With draghandle
Enhance the bottom sheet's usability on touchscreen devices by incorporating a drag handle, which enables swipe functionality.
"use client";
import { useState } from "react";
import { BottomSheet, Button } from "@heathmont/moon-core-tw";
export const WithDraghandle = () => {
const [isOpen, setIsOpen] = useState(false);
const closeBottomSheet = () => {
setIsOpen(false);
};
const openBottomSheet = () => {
setIsOpen(true);
};
return (
<>
<Button variant="outline" onClick={openBottomSheet}>
BottomSheet with Draghandle
</Button>
<BottomSheet open={isOpen} onClose={closeBottomSheet}>
<BottomSheet.Panel>
<BottomSheet.Draghandle />
<div className="flex grow items-center justify-center bg-jiren text-piccolo">
BottomSheet content
</div>
</BottomSheet.Panel>
<BottomSheet.Backdrop />
</BottomSheet>
</>
);
};
export default WithDraghandle;
With title
To add a title to your bottom sheet, insert the <BottomSheet.Title />
sub-component inside <Bottomsheet.Panel />
.
"use client";
import { useState } from "react";
import { BottomSheet, Button } from "@heathmont/moon-core-tw";
export const WithTitle = () => {
const [isTitleOpen, setIsTitleOpen] = useState(false);
const closeTitleBottomSheet = () => {
setIsTitleOpen(false);
};
const openTitleBottomSheet = () => {
setIsTitleOpen(true);
};
const [isMediumOpen, setIsMediumOpen] = useState(false);
const closeMediumBottomSheet = () => {
setIsMediumOpen(false);
};
const openMediumBottomSheet = () => {
setIsMediumOpen(true);
};
return (
<>
<div>
<Button variant="outline" onClick={openTitleBottomSheet}>
BottomSheet with Title
</Button>
<BottomSheet open={isTitleOpen} onClose={closeTitleBottomSheet}>
<BottomSheet.Panel>
<BottomSheet.Title>BottomSheet Title</BottomSheet.Title>
<div className="flex grow items-center justify-center bg-jiren text-piccolo">
BottomSheet content
</div>
</BottomSheet.Panel>
<BottomSheet.Backdrop />
</BottomSheet>
</div>
<div>
<Button variant="outline" onClick={openMediumBottomSheet}>
BottomSheet with Title and Draghandle
</Button>
<BottomSheet open={isMediumOpen} onClose={closeMediumBottomSheet}>
<BottomSheet.Panel>
<BottomSheet.Draghandle>
<BottomSheet.Title>BottomSheet Title</BottomSheet.Title>
</BottomSheet.Draghandle>
<div className="flex grow items-center justify-center bg-jiren text-piccolo">
BottomSheet content
</div>
</BottomSheet.Panel>
<BottomSheet.Backdrop />
</BottomSheet>
</div>
</>
);
};
export default WithTitle;
Customization
In case you need to add some extra styling to the <BottomSheet />
component, you can easily use the className
property. It applies not only to the <BottomSheet />
component itself but also to all its sub-components - <BottomSheet.Panel />
, <BottomSheet.Draghandle />
, <BottomSheet.Title />
, and <BottomSheet.Backdrop />
.
"use client";
import { useState } from "react";
import { BottomSheet, Button } from "@heathmont/moon-core-tw";
export const Customization = () => {
const [isOpen, setIsOpen] = useState(false);
const closeBottomSheet = () => {
setIsOpen(false);
};
const openBottomSheet = () => {
setIsOpen(true);
};
return (
<>
<Button variant="outline" onClick={openBottomSheet}>
Customized BottomSheet
</Button>
<BottomSheet open={isOpen} onClose={closeBottomSheet}>
<BottomSheet.Panel className="bg-raditz p-4">
<BottomSheet.Draghandle className="after:bg-piccolo">
<BottomSheet.Title className="pt-2 text-goten">
BottomSheet Title
</BottomSheet.Title>
</BottomSheet.Draghandle>
<div className="flex grow items-center justify-center bg-jiren text-piccolo">
BottomSheet content
</div>
</BottomSheet.Panel>
<BottomSheet.Backdrop />
</BottomSheet>
</>
);
};
export default Customization;
Custom root element
By default, BottomSheet content is inserted as a portal directly into the document.body. If it's necessary to insert a component into a custom DOM node, follow these steps:
- Add the following HTML code in the location where the component should be rendered:
<div id="headlessui-portal-root"><div /></div>
- Set a prop rootId to the
<BottomSheet>
component with the id of the root element. Example:
<BottomSheet rootId="__next">...</BottomSheet>
By doing this, the "inert" attribute that was added to an ancestor will be removed automatically, and the BottomSheet will become interactive.
"use client";
import { useState } from "react";
import { BottomSheet, Button } from "@heathmont/moon-core-tw";
const addElement = () => {
const portalRootDiv = document.createElement("div");
portalRootDiv.id = "headlessui-portal-root";
const childrenDiv = document.createElement("div");
portalRootDiv.appendChild(childrenDiv);
const currentDiv = document.getElementById("root-element");
currentDiv?.appendChild(portalRootDiv);
};
const deleteElement = () => {
const portalRootDiv = document.getElementById("headlessui-portal-root");
portalRootDiv?.remove();
};
export const RootPortal = () => {
const [isOpen, setIsOpen] = useState(false);
const closeBottomSheet = () => {
setIsOpen(false);
setTimeout(() => {
deleteElement();
}, 300);
};
const openBottomSheet = () => {
addElement();
setIsOpen(true);
};
return (
<div id="root-element">
<Button variant="outline" onClick={openBottomSheet}>
BottomSheet
</Button>
<BottomSheet open={isOpen} rootId="__next">
<BottomSheet.Panel onClose={closeBottomSheet}>
<BottomSheet.Draghandle />
<div className="flex grow items-center justify-center bg-jiren text-piccolo">
BottomSheet content
</div>
</BottomSheet.Panel>
<BottomSheet.Backdrop onClose={closeBottomSheet} />
</BottomSheet>
</div>
);
};
export default RootPortal;
BottomSheet
These are props specific to the BottomSheet component:
Name | Type | Default |
---|---|---|
onClose* | "() => void" | - |
open* | boolean | - |
rootId | string | - |
className | string | - |
Properties indicated with * are required.
BottomSheet.Panel
These are props specific to the BottomSheet.Panel component:
Name | Type | Default |
---|---|---|
className | string | - |
onClose | "() => void" | - |
BottomSheet.Title
These are props specific to the BottomSheet.Title component:
Name | Type | Default |
---|---|---|
className | string | - |
BottomSheet.Draghandle
These are props specific to the BottomSheet.Draghandle component:
Name | Type | Default |
---|---|---|
className | string | - |
BottomSheet.Backdrop
These are props specific to the BottomSheet.Backdrop component:
Name | Type | Default |
---|---|---|
className | string | - |