{"id":14961159,"url":"https://github.com/temzasse/react-modal-sheet","last_synced_at":"2026-03-13T11:00:57.339Z","repository":{"id":37323700,"uuid":"264747340","full_name":"Temzasse/react-modal-sheet","owner":"Temzasse","description":"Flexible bottom sheet component built with Motion to provide buttery smooth UX while keeping accessibility in mind 🪐","archived":false,"fork":false,"pushed_at":"2026-03-03T11:32:41.000Z","size":112879,"stargazers_count":1116,"open_issues_count":23,"forks_count":98,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-03-03T14:34:22.034Z","etag":null,"topics":["accessibility","bottom-sheet","framer-motion","modal","motion","reactjs"],"latest_commit_sha":null,"homepage":"https://temzasse.github.io/react-modal-sheet/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Temzasse.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-05-17T20:06:18.000Z","updated_at":"2026-03-03T11:32:45.000Z","dependencies_parsed_at":"2023-02-09T19:31:33.450Z","dependency_job_id":"b54f2219-fd57-491a-b741-85185cf43d51","html_url":"https://github.com/Temzasse/react-modal-sheet","commit_stats":{"total_commits":171,"total_committers":14,"mean_commits":"12.214285714285714","dds":0.0935672514619883,"last_synced_commit":"1c0ddb73647ef122405ad634239b7ffd6bf35c50"},"previous_names":[],"tags_count":55,"template":false,"template_full_name":null,"purl":"pkg:github/Temzasse/react-modal-sheet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Temzasse%2Freact-modal-sheet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Temzasse%2Freact-modal-sheet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Temzasse%2Freact-modal-sheet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Temzasse%2Freact-modal-sheet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Temzasse","download_url":"https://codeload.github.com/Temzasse/react-modal-sheet/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Temzasse%2Freact-modal-sheet/sbom","scorecard":{"id":139041,"data":{"date":"2025-08-04","repo":{"name":"github.com/Temzasse/react-modal-sheet","commit":"6f3e85498fe8cfff88afc3242252c7f75e5033f0"},"scorecard":{"version":"v5.2.1-28-gc1d103a9","commit":"c1d103a9bb9f635ec7260bf9aa0699466fa4be0e"},"score":3.1,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/24 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#code-review"}},{"name":"Maintained","score":2,"reason":"0 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/main.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#branch-protection"}},{"name":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/Temzasse/react-modal-sheet/main.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/Temzasse/react-modal-sheet/main.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 7 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":2,"reason":"8 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-xffm-g5w8-qvg7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-qpjv-v59x-3qc4","Warn: Project is vulnerable to: GHSA-67rr-84xm-4c7r","Warn: Project is vulnerable to: GHSA-3h52-269p-cp9r","Warn: Project is vulnerable to: GHSA-f82v-jwr5-mffw","Warn: Project is vulnerable to: GHSA-cpj6-fhp6-mr6j","Warn: Project is vulnerable to: GHSA-859w-5945-r5v3"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-16T07:29:04.482Z","repository_id":37323700,"created_at":"2025-08-16T07:29:04.483Z","updated_at":"2025-08-16T07:29:04.483Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30466310,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T11:00:43.441Z","status":"ssl_error","status_checked_at":"2026-03-13T11:00:23.173Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["accessibility","bottom-sheet","framer-motion","modal","motion","reactjs"],"created_at":"2024-09-24T13:23:59.939Z","updated_at":"2026-03-13T11:00:57.324Z","avatar_url":"https://github.com/Temzasse.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align='center'\u003e\n  \u003cimg src=\"media/hero.jpg\" alt=\"React Modal Sheet logo\"/\u003e\n\u003cp/\u003e\n\n\u003cdiv align=\"center\" \u003e\n  \u0026middot;\n  \u003ci\u003eFlexible bottom sheet component for your React apps\u003c/i\u003e\n  \u0026middot;\n  \u003cbr/\u003e\n  \u003cbr/\u003e\n  \u003cimg alt=\"npm version\" src=\"https://img.shields.io/npm/v/react-modal-sheet?style=for-the-badge\"\u003e\n  \u003cimg alt=\"npm license\" src=\"https://img.shields.io/npm/l/react-modal-sheet?style=for-the-badge\"\u003e\n  \u003cimg alt=\"npm downloads\" src=\"https://img.shields.io/npm/dm/react-modal-sheet?style=for-the-badge\"\u003e\n  \u003cbr/\u003e\n  \u003cbr/\u003e\n\u003c/div\u003e\n\n| ![](media/example-apple-maps.gif) | ![](media/example-apple-music.gif) | ![](media/example-snap-points.gif) | ![](media/example-scrollable.gif) |\n| :-------------------------------: | :--------------------------------: | :--------------------------------: | :-------------------------------: |\n\n## 📦 Installation\n\n```sh\nnpm install react-modal-sheet\n```\n\n### Peer dependencies\n\nThe gestures and animations are handled by the excellent [Motion](https://motion.dev/) library so before you can start using this library you need to install `motion`:\n\n```sh\nnpm install motion\n```\n\n---\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003e📚 Table of contents\u003c/strong\u003e\u003c/summary\u003e\n  \n  - [What's new in v5](#-whats-new-in-v5)\n  - [Usage](#-usage)\n  - [Props](#%EF%B8%8F-props)\n  - [Methods and properties](#%EF%B8%8F-methods-and-properties)\n  - [Compound Components](#-compound-components)\n  - [Advanced usage](#-advanced-usage)\n  - [Customization](#-customization)\n  - [Accessibility](#%EF%B8%8F-accessibility)\n  - [Troubleshooting](#-troubleshooting)\n\u003c/details\u003e\n\n---\n\n## 🎉 What's new in v5\n\nVersion 5 introduces several major improvements and breaking changes:\n\n### 🔄 Breaking Changes\n\n- **Removed `Sheet.Scroller`**: Scrolling is now handled automatically by `Sheet.Content`\n  - See [Scrolling behavior](#-scrolling-behavior) section for details\n  - See [Making scrollable sheet content always reachable](#making-scrollable-sheet-content-always-reachable) for guidance on how to implement auto padding behavior\n- **Snap point order reversed**: Snap points now use ascending order (e.g., `[0, 0.5, 1]` instead of `[1, 0.5, 0]`)\n  - This aligns better with other bottom sheet libraries and makes more intuitive sense\n- **Detent prop values changed**:\n  - `\"full-height\"` → `\"default\"`\n  - `\"content-height\"` → `\"content\"`\n  - New `\"full\"` detent for viewport-filling sheets\n\n### ✨ New Features\n\n- **Built-in keyboard avoidance**: Best-effort automatic virtual keyboard handling with the `avoidKeyboard` prop\n- **Enhanced scroll control**: Dynamic `disableScroll` and `disableDrag` functions with scroll state\n- **Improved snap point handling**: Better snap point calculation and more natural snapping between points\n- **New sheet properties**: Access `height` and `yInverted` motion values via ref\n- **Prevent dismissal**: New `disableDismiss` prop to prevent sheet from being closed by user gestures\n\n### 🔧 Migration from v4\n\n1. Remove all `Sheet.Scroller` components - content is now scrollable by default\n2. Reverse your snap point arrays: `[1, 0.5, 0]` → `[0, 0.5, 1]`\n3. Update detent props: `detent=\"full-height\"` → `detent=\"default\"`\n4. Review virtual keyboard handling - it's now automatic with `avoidKeyboard={true}`\n\n---\n\n## 💻 Usage\n\n```tsx\nimport { Sheet } from 'react-modal-sheet';\nimport { useState } from 'react';\n\nfunction Example() {\n  const [isOpen, setOpen] = useState(false);\n\n  return (\n    \u003c\u003e\n      \u003cbutton onClick={() =\u003e setOpen(true)}\u003eOpen sheet\u003c/button\u003e\n\n      \u003cSheet isOpen={isOpen} onClose={() =\u003e setOpen(false)}\u003e\n        \u003cSheet.Container\u003e\n          \u003cSheet.Header /\u003e\n          \u003cSheet.Content\u003e{/* Your sheet content goes here */}\u003c/Sheet.Content\u003e\n        \u003c/Sheet.Container\u003e\n        \u003cSheet.Backdrop /\u003e\n      \u003c/Sheet\u003e\n    \u003c/\u003e\n  );\n}\n```\n\nThe `Sheet` component follows the [Compound Component pattern](https://kentcdodds.com/blog/compound-components-with-react-hooks) in order to provide a flexible yet powerful API for creating highly customizable bottom sheet components.\n\nSince the final bottom sheet is composed from smaller building blocks (`Container`, `Content`, `Header`, and `Backdrop`) you are in total control over the rendering output. So for example, if you don't want to have any backdrop in your sheet then you can just skip rendering it instead of passing some prop like `renderBackdrop={false}` to the main sheet component. Cool huh? 😎\n\nAlso, by constructing the sheet from smaller pieces makes it easier to apply any necessary accessibility related properties to the right components without requiring the main sheet component to be aware of them. You can read more about accessibility in the Accessibility section below.\n\n## 🎛️ Props\n\n| Name                    | Required | Default                              | Description                                                                                                                                                                                                                                                                                                                     |\n| ----------------------- | -------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `children`              | yes      |                                      | Use `Sheet.Container/Content/Header/Backdrop` to compose your bottom sheet.                                                                                                                                                                                                                                                     |\n| `isOpen`                | yes      |                                      | Boolean that indicates whether the sheet is open or not.                                                                                                                                                                                                                                                                        |\n| `onClose`               | yes      |                                      | Callback fn that is called when the sheet is closed by the user.                                                                                                                                                                                                                                                                |\n| `avoidKeyboard`         | no       | true                                 | Automatically avoid the virtual keyboard by adding bottom padding when the keyboard is open. Only works on mobile devices with [Virtual Keyboard](https://developer.mozilla.org/en-US/docs/Web/API/VirtualKeyboard_API) or [Visual Viewport](https://developer.mozilla.org/en-US/docs/Web/API/Visual_Viewport_API) API support. |\n| `disableDrag`           | no       | false                                | Disable drag for the whole sheet.                                                                                                                                                                                                                                                                                               |\n| `disableDismiss`        | no       | false                                | Disable dismissing the sheet via dragging or high velocity swipe. When enabled, the sheet can only be closed programmatically.                                                                                                                                                                                                  |\n| `disableScrollLocking`  | no       | false                                | Disable scroll locking for the `body` element while sheet is open. Can be useful if you face issues with input elements and the iOS virtual keyboard. See related [issue](https://github.com/Temzasse/react-modal-sheet/issues/135).                                                                                            |\n| `detent`                | no       | `'default'`                          | The [detent](https://developer.apple.com/design/human-interface-guidelines/components/presentation/sheets#ios-ipados) in which the sheet should be in when opened. Available values: `'default'`, `'content'`, or `'full'`.                                                                                                     |\n| `onOpenStart`           | no       |                                      | Callback fn that is called when the sheet opening animation starts.                                                                                                                                                                                                                                                             |\n| `onOpenEnd`             | no       |                                      | Callback fn that is called when the sheet opening animation is completed.                                                                                                                                                                                                                                                       |\n| `onCloseStart`          | no       |                                      | Callback fn that is called when the sheet closing animation starts.                                                                                                                                                                                                                                                             |\n| `onCloseEnd`            | no       |                                      | Callback fn that is called when the sheet closing animation is completed.                                                                                                                                                                                                                                                       |\n| `onSnap`                | no       |                                      | Callback fn that is called with the current snap point index when the sheet snaps to a new snap point. Requires `snapPoints` prop.                                                                                                                                                                                              |\n| `snapPoints`            | no       |                                      | Eg. `[0, 0.5, 100, 1]` - where positive values are pixels from the bottom of the sheet and negative from the top. Values between 0-1 represent percentages, eg. `0.5` means 50% of sheet height. **Must be in ascending order and should include 0 (closed) and 1 (fully open).**                                               |\n| `initialSnap`           | no       | 0                                    | Initial snap point when sheet is opened (index from `snapPoints`).                                                                                                                                                                                                                                                              |\n| `modalEffectRootId`     | no       |                                      | The id of the element where the main app is mounted, eg. \"root\". Enables [iOS modal effect](#-ios-modal-view-effect).                                                                                                                                                                                                           |\n| `modalEffectThreshold`  | no       | 0                                    | Threshold value between 0-1 which determines when the iOS modal effect will start while dragging the sheet - `0` corresponding to the start of the drag (0% has been dragged into view) and `1` corresponding to the end of the drag (100% of the sheet is visible).                                                            |\n| `tweenConfig`           | no       | `{ ease: 'easeOut', duration: 0.2 }` | Overrides the config for the sheet [tween](https://motion.dev/docs/react-transitions#tween) transition when the sheet is opened, closed, or snapped to a point.                                                                                                                                                                 |\n| `mountPoint`            | no       | `document.body`                      | HTML element that should be used as the mount point for the sheet.                                                                                                                                                                                                                                                              |\n| `prefersReducedMotion`  | no       | false                                | Skip sheet animations (sheet instantly snaps to desired location).                                                                                                                                                                                                                                                              |\n| `dragVelocityThreshold` | no       | 500                                  | How fast the sheet must be flicked down to close. Higher values make the sheet harder to close.                                                                                                                                                                                                                                 |\n| `dragCloseThreshold`    | no       | 0.6                                  | The portion of the sheet which must be dragged off-screen before it will close.                                                                                                                                                                                                                                                 |\n| `unstyled`              | no       | false                                | Remove all decorative styles and only apply base functional styles. Useful when you want to completely customize the sheet appearance.                                                                                                                                                                                          |\n\n## ⚙️ Methods and properties\n\n### `snapTo(index)`\n\nImperative method that can be accessed via a ref for snapping to a snap point in given index.\n\n```tsx\nimport { Sheet, SheetRef } from 'react-modal-sheet';\nimport { useState, useRef } from 'react';\n\nconst snapPoints = [0, 0.5, 1];\n\nfunction SnapExample() {\n  const [isOpen, setOpen] = useState(false);\n  const ref = useRef\u003cSheetRef\u003e(null);\n  const snapTo = (i: number) =\u003e ref.current?.snapTo(i);\n\n  return (\n    \u003c\u003e\n      \u003cbutton onClick={() =\u003e setOpen(true)}\u003eOpen sheet\u003c/button\u003e\n\n      {/* Opens to 50% since initial index is 1 */}\n      \u003cSheet\n        ref={ref}\n        isOpen={isOpen}\n        onClose={() =\u003e setOpen(false)}\n        initialSnap={1}\n        snapPoints={snapPoints}\n        onSnap={(snapIndex) =\u003e\n          console.log('\u003e Current snap point index:', snapIndex)\n        }\n      \u003e\n        \u003cSheet.Container\u003e\n          \u003cSheet.Content\u003e\n            \u003cbutton onClick={() =\u003e snapTo(0)}\u003eSnap to index 0\u003c/button\u003e\n            \u003cbutton onClick={() =\u003e snapTo(1)}\u003eSnap to index 1\u003c/button\u003e\n            \u003cbutton onClick={() =\u003e snapTo(2)}\u003eSnap to index 2\u003c/button\u003e\n          \u003c/Sheet.Content\u003e\n        \u003c/Sheet.Container\u003e\n      \u003c/Sheet\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n### Motion values `y`, `yInverted` and `height`\n\nThe `y` value is an internal [MotionValue](https://motion.dev/docs/react-motion-value) that represents the distance to the top most position of the sheet when it is fully open. So for example the `y` value is zero when the sheet is completely open.\n\nThe `yInverted` value is the inverse of the `y` value and represents the distance from the bottom of the sheet. This can be useful for certain animations or calculations.\n\nThe `height` value represents the current height of the sheet in pixels.\n\nAll these values can be accessed via a ref, similar to the `snapTo` method.\n\nBelow you can see an example of how to use these values:\n\n```tsx\nimport { Sheet, SheetRef } from 'react-modal-sheet';\nimport { useTransform } from 'motion/react';\nimport { useState, useRef } from 'react';\n\nfunction RefExample() {\n  const [isOpen, setOpen] = useState(false);\n  const ref = useRef\u003cSheetRef\u003e(null);\n\n  const background = useTransform(() =\u003e {\n    const y = ref.current?.y.get() ?? 0;\n    return y \u003e 100 ? 'lightblue' : 'lightcoral';\n  });\n\n  function doSomething() {\n    console.log('\u003e Current y value:', ref.current?.y.get());\n    console.log('\u003e Current yInverted value:', ref.current?.yInverted.get());\n    console.log('\u003e Current height value:', ref.current?.height);\n  }\n\n  return (\n    \u003c\u003e\n      \u003cbutton onClick={() =\u003e setOpen(true)}\u003eOpen sheet\u003c/button\u003e\n\n      \u003cSheet ref={ref} isOpen={isOpen} onClose={() =\u003e setOpen(false)}\u003e\n        \u003cSheet.Container\u003e\n          \u003cSheet.Content\u003e\n            \u003cmotion.div style={{ background }}\u003e\n              Use animated y value in some way\n            \u003c/motion.div\u003e\n            {/* Your content here */}\n          \u003c/Sheet.Content\u003e\n        \u003c/Sheet.Container\u003e\n      \u003c/Sheet\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n#### Making scrollable sheet content always reachable\n\nOne common use case for the `y` value is to apply padding bottom to the sheet content\nwhen the sheet is not fully open. This ensures that the content is always reachable even when the sheet is partially open, eg. when using snap points.\n\n\u003e [!NOTE]\n\u003e Prior to v5 this was a built-in behavior of `Sheet.Scroller` via `autoPadding` prop but since `Sheet.Scroller` has been removed in v5 in favor of `Sheet.Content` handling scrolling internally, you can now implement this behavior yourself with the help of the `y` motion value.\n\u003e\n\u003e Also take a look at the [Creating custom scrollers](#creating-custom-scrollers) section for more advanced use cases when you need to implement your own scroller and potentially apply this padding behavior to that.\n\n```tsx\nimport { Sheet, SheetRef } from 'react-modal-sheet';\nimport { useTransform } from 'motion/react';\nimport { useState, useRef } from 'react';\n\nconst snapPoints = [0, 0.5, 1];\n\nfunction PaddingExample() {\n  const [isOpen, setOpen] = useState(false);\n  const ref = useRef\u003cSheetRef\u003e(null);\n\n  // Add padding bottom based on how far the sheet is from being fully open\n  const paddingBottom = useTransform(() =\u003e {\n    return ref.current?.y.get() ?? 0;\n  });\n\n  return (\n    \u003c\u003e\n      \u003cbutton onClick={() =\u003e setOpen(true)}\u003eOpen sheet\u003c/button\u003e\n\n      \u003cSheet\n        ref={ref}\n        isOpen={isOpen}\n        onClose={() =\u003e setOpen(false)}\n        snapPoints={snapPoints}\n        initialSnap={1}\n      \u003e\n        \u003cSheet.Container\u003e\n          \u003cSheet.Header /\u003e\n          \u003cSheet.Content\n            // 👇 Apply styles to the inner scroller element\n            scrollStyle={{ paddingBottom }}\n            // 💡 Recommendation: disable drag for the content so that it doesn't interfere with scrolling\n            disableDrag\n          \u003e\n            This content will always be reachable even when the sheet is not\n            fully open.\n          \u003c/Sheet.Content\u003e\n        \u003c/Sheet.Container\u003e\n      \u003c/Sheet\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n### Detents\n\nBy default the sheet will take the full height of the page minus top padding and safe area inset. The `detent` prop controls the sheet's height behavior:\n\n- `\"default\"` - Sheet takes full height minus safe areas (default behavior)\n- `\"content\"` - Sheet height is based on its content\n- `\"full\"` - Sheet takes the entire viewport height with no safe area insets\n\n```tsx\nfunction DetentExample() {\n  return (\n    \u003cSheet detent=\"content\"\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Content\u003e\n          \u003cdiv style={{ height: 200 }}\u003eSome content\u003c/div\u003e\n        \u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\nWhen using `detent=\"content\"` and the sheet height changes dynamically, the sheet will grow until it hits the maximum default height, after which it becomes scrollable.\n\nIt is possible to use snap points with `detent=\"content\"` **but** the snap points are restricted by the content height. For example if one of the snap points is 800px and the sheet height is only 700px then snapping to the 800px snap point would only snap to 700px since otherwise the sheet would become detached from the bottom.\n\n\u003e ℹ️ If you are wondering where the term `detent` comes from it's from Apple's [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/components/presentation/sheets#ios-ipados).\n\n## 🧩 Compound Components\n\n### `Sheet`\n\nSheet is the root element that wraps the whole sheet. It renders a fixed positioned wrapper element that covers the whole screen to contain the actual sheet that is animated in (don't worry the root element is not interactive when the sheet is closed). All sheet compound components should be rendered within `Sheet`.\n\n\u003e 🖥 Rendered element: `motion.div`.\n\n### `Sheet.Container`\n\nSheet container is positioned above the sheet backdrop and by default adds a small shadow and rounded corners to the sheet. `Sheet.Content` and `Sheet.Header` should be rendered inside `Sheet.Container`.\n\n\u003e 🖥 Rendered element: `motion.div`.\n\n### `Sheet.Header`\n\nSheet header acts as a drag target and has a dragging direction indicator. Rendering any children inside `Sheet.Header` replaces the default header.\n\n\u003e 🖥 Rendered element: `motion.div`.\n\n#### Header props\n\n| Name          | Required | Default | Description                        |\n| ------------- | -------- | ------- | ---------------------------------- |\n| `disableDrag` | no       | false   | Disable drag for the sheet header. |\n\n### `Sheet.DragIndicator`\n\nSheet drag indicator renders two small animated lines that indicate the dragging direction. This component is automatically included in `Sheet.Header` but can be used standalone for custom header designs. The indicator lines animate with the sheet's drag state to provide visual feedback.\n\n\u003e 🖥 Rendered element: Two `motion.span` elements.\n\n#### Usage example\n\n```tsx\nfunction CustomHeaderExample() {\n  return (\n    \u003cSheet\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Header\u003e\n          \u003cdiv style={{ padding: '16px', textAlign: 'center' }}\u003e\n            \u003ch2\u003eCustom Header\u003c/h2\u003e\n            \u003cSheet.DragIndicator /\u003e\n          \u003c/div\u003e\n        \u003c/Sheet.Header\u003e\n        \u003cSheet.Content\u003e\n          \u003cp\u003eSheet content goes here...\u003c/p\u003e\n        \u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\n### `Sheet.Content`\n\nSheet content acts as a drag target and handles scrollable content internally. It automatically manages scroll behavior and keyboard avoidance.\n\n\u003e 🖥 Rendered element: `motion.div` (with internal scroller).\n\n#### Content props\n\n| Name            | Required | Default | Description                                                                                                                                                                                 |\n| --------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `disableDrag`   | no       | false   | Disable drag for the sheet content. Can be a boolean or a function that receives `{ scrollPosition, currentSnap }` and returns a boolean for dynamic control.                               |\n| `disableScroll` | no       | false   | Disable scrolling. Can be a boolean or a function that receives `{ scrollPosition, currentSnap }` and returns a boolean. Useful for making content only scrollable at specific snap points. |\n| `scrollRef`     | no       |         | Optional ref to the internal scroll container for accessing scroll methods.                                                                                                                 |\n\n### `Sheet.Backdrop`\n\nSheet backdrop is a translucent overlay that helps to separate the sheet from it's background. By default the backdrop doesn't have any interaction attached to it but if you, for example, want to close the sheet when the backdrop is clicked you can provide tap handler to it which will change the rendered element from `div` to `button`.\n\n**⚠️ Note:** as the element is a motion component you need to use [`onTap`](https://motion.dev/docs/react-gestures#tap) instead of `onClick` if you want to add a click handler to it.\n\n\u003e 🖥 Rendered element: `motion.div` or `motion.button`.\n\n## ✨ Advanced behaviors\n\n### ⌨️ Virtual keyboard avoidance\n\nReact Modal Sheet v5 includes built-in virtual keyboard avoidance that works automatically on mobile devices.\nWhen the `avoidKeyboard` prop is enabled (which is the default), the sheet will automatically add bottom padding to avoid the virtual keyboard covering input elements.\n\n```tsx\nfunction KeyboardExample() {\n  return (\n    \u003cSheet avoidKeyboard\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Header /\u003e\n        \u003cSheet.Content\u003e\n          \u003cinput placeholder=\"Your input here\" /\u003e\n          \u003ctextarea placeholder=\"Your message here\" /\u003e\n          {/* More form elements */}\n        \u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\nVirtual keyboard avoidance consists of several key features:\n\n- Uses the `env(keyboard-inset-height)` from [Virtual Keyboard API](https://developer.mozilla.org/en-US/docs/Web/API/VirtualKeyboard_API) and fallbacks to [Visual Viewport API](https://developer.mozilla.org/en-US/docs/Web/API/Visual_Viewport_API) and custom `keyboard-inset-height` CSS environment variable.\n- Works automatically on mobile devices with virtual keyboard support.\n- Applies dynamic padding to keep content visible when the keyboard opens.\n- Scrolls the focused input into view if it's covered by the keyboard.\n- Disables drag gestures while the keyboard is open to prevent glitches.\n- When using snap points, the sheet will be automatically moved up to the last snap point to ensure that all content is reachable (even when the content is scrollable) within the sheet when the keyboard is open.\n\n\u003e [!NOTE]\n\u003e Avoiding the virtual keyboard is a complex problem and the built-in avoidance may not work perfectly in all cases or on all devices. The library does its best to handle the most common scenarios but there may be edge cases where it doesn't work as expected.\n\u003e\n\u003e Mobile browsers, eg. Android Chrome, may try to automatically move the focused input into view when the keyboard opens which might interfere with the built-in keyboard avoidance behavior causing the sheet content to jump around.\n\u003e\n\u003e If you need to disable the built-in keyboard avoidance, set `avoidKeyboard={false}`.\n\n#### Managing virtual keyboard manually\n\nIf the built-in keyboard avoidance doesn't work for your use case, you can disable it with `avoidKeyboard={false}` and manage the keyboard state manually with the help of the `useVirtualKeyboard` hook.\n\n```tsx\nimport { Sheet, useVirtualKeyboard } from 'react-modal-sheet';\n\nfunction ManualKeyboardExample() {\n  const [isOpen, setOpen] = useState(false);\n\n  // This hook manages the `--keyboard-inset-height` CSS variable for you\n  useVirtualKeyboard({ isEnabled: isOpen });\n\n  return (\n    // Disable default keyboard avoidance\n    \u003cSheet avoidKeyboard={false} isOpen={isOpen} onClose={() =\u003e setOpen(false)}\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Header /\u003e\n        \u003cSheet.Content\n          // Apply keyboard height as padding bottom to keep content above the keyboard\n          style={{ paddingBottom: 'var(--keyboard-inset-height)' }}\n        \u003e\n          {/* Your content here */}\n        \u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\nFor a more detailed example of using `useVirtualKeyboard` see the [Creating custom scrollers](#creating-custom-scrollers) section below.\n\n### 📱 Scrolling behavior\n\nThe `Sheet.Content` component manages scroll interactions internally and provides several ways to control scrolling:\n\n#### Basic scrollable content\n\n```tsx\nfunction ScrollableExample() {\n  return (\n    \u003cSheet\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Header /\u003e\n        \u003cSheet.Content\u003e\n          {/* Long content that needs scrolling */}\n          \u003cdiv style={{ height: '200vh' }}\u003eLong content here...\u003c/div\u003e\n        \u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\n#### Controlling scroll behavior with snap points\n\nYou can disable scrolling conditionally using the `disableScroll` prop. This is useful when you want content to be scrollable only at certain snap points:\n\n```tsx\nconst snapPoints = [0, 0.5, 1];\n\nfunction ConditionalScrollExample() {\n  return (\n    \u003cSheet snapPoints={snapPoints} initialSnap={1}\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Header /\u003e\n        \u003cSheet.Content\n          // Only allow scrolling when at the top snap point (index 2)\n          disableScroll={(state) =\u003e state.currentSnap !== 2}\n        \u003e\n          \u003cdiv style={{ height: '200vh' }}\u003eLong content here...\u003c/div\u003e\n        \u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\n#### Dynamic drag control\n\nSimilarly, you can control when dragging is enabled based on scroll position or snap point:\n\n```tsx\nfunction DynamicDragExample() {\n  return (\n    \u003cSheet\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Header /\u003e\n        \u003cSheet.Content\n          // Disable drag when not scrolled to top\n          disableDrag={(state) =\u003e state.scrollPosition !== 'top'}\n        \u003e\n          \u003cdiv style={{ height: '200vh' }}\u003eLong content here...\u003c/div\u003e\n        \u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\nThe scroll state provides:\n\n- `scrollPosition`: `'top'`, `'bottom'`, `'middle'`, or `undefined`\n- `currentSnap`: Current snap point index (if using snap points)\n\n#### Creating custom scrollers\n\nIf you need more control over the scrollable area, eg. when you don't want the whole content area to be scrollable, you can disable the default scrolling behavior of the `Sheet.Content` and implement your own scroller with the help of `useScrollPosition` and `useVirtualKeyboard` utility hooks.\n\n\u003e [!NOTE]\n\u003e These hooks are internally used by the `Sheet.Content` component so you shouldn't need to use them unless you are implementing a custom scroller. However they are general purpose hooks so you can use them in other parts of your app as well if you want 😊\n\n```tsx\nimport {\n  Sheet,\n  useScrollPosition,\n  useVirtualKeyboard,\n} from 'react-modal-sheet';\n\nfunction CustomScrollerExample() {\n  const [isOpen, setOpen] = useState(false);\n\n  const { scrollRef, scrollPosition } = useScrollPosition({\n    isEnabled: isOpen,\n  });\n\n  const { keyboardHeight } = useVirtualKeyboard({\n    isEnabled: isOpen,\n  });\n\n  /**\n   * If you use `var(--keyboard-inset-height)` CSS variable you can just call\n   * `useVirtualKeyboard()` without destructuring anything from it:\n   *\n   * useVirtualKeyboard({ isEnabled: isOpen });\n   */\n\n  return (\n    // Disable default keyboard avoidance\n    \u003cSheet avoidKeyboard={false} isOpen={isOpen} onClose={() =\u003e setOpen(false)}\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Header /\u003e\n        \u003cSheet.Content\n          // Disable default scrolling\n          disableScroll\n          // Dragging is still managed by `Sheet.Content` so we control it here\n          disableDrag={scrollPosition !== 'top'}\n        \u003e\n          \u003cdiv\n            style={{\n              // Use CSS variable managed by `useVirtualKeyboard`\n              paddingBottom: 'var(--keyboard-inset-height)',\n              // Alternatively apply keyboard padding via pixel value (maybe you want to add some extra padding to it)\n              paddingBottom: keyboardHeight,\n              // Some layout styles for custom content\n              height: '100%',\n              display: 'grid',\n              gridTemplateRows: 'auto 1fr auto',\n            }}\n          \u003e\n            \u003cdiv\u003eSome content here...\u003c/div\u003e\n\n            \u003cdiv\n              // Pass ref to the custom scroller so we can track its scroll position\n              ref={scrollRef}\n              // Make it scrollable\n              style={{ overflowY: 'auto' }}\n            \u003e\n              \u003cdiv style={{ height: '400vh' }}\u003eLong content here...\u003c/div\u003e\n            \u003c/div\u003e\n\n            \u003cdiv\u003eMore content here...\u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\n### 🪟 iOS Modal View effect\n\nIn addition to the `Sheet.Backdrop` it's possible to apply a scaling effect to the main app element to highlight the modality of the bottom sheet. This effect mimics the [iOS Modal View](https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/modality/) presentation style to bring more focus to the sheet and add some delight to the user experience.\n\n| ![iOS Modal Effect Demo 1](media/example-apple-music.gif) | ![iOS Modal Effect Demo 2](media/example-snap-points.gif) |\n| :-------------------------------------------------------: | :-------------------------------------------------------: |\n\nTo enable this effect you can provide the id of the root element where your application is mounted:\n\n```tsx\nfunction Example() {\n  return \u003cSheet modalEffectRootId=\"root\"\u003e{/*...*/}\u003c/Sheet\u003e;\n}\n```\n\n\u003e [!IMPORTANT]\n\u003e ⚠️ **Limitations**: Since the effect is applied to the root element it will NOT work as desired if the HTML body element is scrolled down at all. One way to avoid this is to use something like `height: 100vh;` and `overflow: auto;` on the root element to make it fill the whole screen and be scrollable instead of the body element. You can check the example apps [root styles](https://github.com/Temzasse/react-modal-sheet/blob/main/example/src/index.css) for a working example of this.\n\n#### Controlling the effect\n\nYou can control the start threshold of the effect with the `modalEffectThreshold` prop which should have a value between `0` and `1` representing the fraction at which the effect starts relative to the dragging distance. So for example, a value of `0.5` means the effect will start when the sheet is dragged halfway and `0.7` would mean the effect will start when the sheet is dragged 70% of the way to the top. The default value is `0` which means that the effect will start as soon as the sheet is opened or dragged into view.\n\n\u003e [!NOTE]\n\u003e If you are using `snapPoints` the start threshold is calculated based on the\n\u003e second snap point so that when the sheet is snapping to the first snap point,\n\u003e meaning the sheet is fully open, the effect will only apply for that snap point\n\u003e range and not for the whole draggable distance.\n\u003e\n\u003e This gives a more natural UX and follows the native iOS effect more closely.\n\u003e\n\u003e You can still override this behavior by providing a custom `modalEffectThreshold` value.\n\n## 🎨 Customization\n\nThe default styles for the `Sheet` component somewhat follows the styles of the previously mentioned iOS Modal View. However, if these default styles are not to your liking it's easy to make changes to them: you can provide a custom header or you can overwrite any style with CSS via the exposed class names.\n\n### Custom header\n\nAdding a custom header is as simple as providing your own header as the child component to `Sheet.Header`:\n\n```tsx\nfunction Example() {\n  return (\n    \u003cSheet\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Header\u003e\n          \u003cYourCustomSheetHeader /\u003e\n        \u003c/Sheet.Header\u003e\n        \u003cSheet.Content\u003e{/*...*/}\u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\n### Custom styles\n\nYou can add your own styles or override the default sheet styles via the exposed class names. Note that you might need to use `!important` for style overrides since the inner styles are applied as inline styles which have higher specificity.\n\n#### Unstyled mode\n\nFor complete control over the sheet's appearance, you can use the `unstyled` prop to remove all decorative styles while keeping only the essential functional styles.\n\nWhen `unstyled={true}` is set on the root `Sheet` component, it removes decorative styles like: border radius, shadows, and background colors:\n\n```tsx\nfunction UnstyledExample() {\n  return (\n    \u003cSheet unstyled\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Header /\u003e\n        \u003cSheet.Content\u003e{/* Your content */}\u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\nThe `unstyled` prop can also be applied to individual sheet components to selectively remove their decorative styles:\n\n```tsx\nfunction SelectiveUnstyledExample() {\n  return (\n    \u003cSheet\u003e\n      \u003cSheet.Container unstyled\u003e\n        \u003cSheet.Header /\u003e\n        \u003cSheet.Content\u003e{/* Your content */}\u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      {/* Keep the backdrop styled */}\n      \u003cSheet.Backdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\nThis is particularly useful when building custom designs or when integrating with design systems that require complete control over styling.\n\nYou can also remove all styles at the root level and then override it for individual components if you want to keep some default styles:\n\n```tsx\nfunction MixedUnstyledExample() {\n  return (\n    \u003cSheet unstyled\u003e\n      \u003cSheet.Container\u003e\n        \u003cSheet.Header /\u003e\n        \u003cSheet.Content\u003e{/* Your content */}\u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop unstyled={false} /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\n### CSS Modules\n\n```tsx\nimport styles from './styles.css';\n\nfunction Example() {\n  return (\n    \u003cSheet\u003e\n      \u003cSheet.Container className={styles.sheetContainer}\u003e\n        \u003cSheet.Header className={styles.sheetHeader} /\u003e\n        \u003cSheet.Content className={styles.sheetContent}\u003e{/*...*/}\u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop className={styles.sheetBackdrop} /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n```\n\n```css\n/* styles.css */\n\n.sheetContainer {\n  /* custom styles */\n}\n.sheetHeader {\n  /* custom styles */\n}\n.sheetContent {\n  /* custom styles */\n}\n.sheetBackdrop {\n  /* custom styles */\n}\n```\n\n#### Vanilla CSS\n\nYou can also use vanilla CSS to override the styles by targeting the exposed class names:\n\n```css\n.react-modal-sheet-backdrop {\n  /* custom styles */\n}\n.react-modal-sheet-container {\n  /* custom styles */\n}\n.react-modal-sheet-header {\n  /* custom styles */\n}\n.react-modal-sheet-header-container {\n  /* custom styles */\n}\n.react-modal-sheet-drag-indicator-container {\n  /* custom styles */\n}\n.react-modal-sheet-drag-indicator {\n  /* custom styles */\n}\n.react-modal-sheet-content {\n  /* custom styles */\n}\n```\n\n#### CSS-in-JS\n\n```tsx\nimport { styled } from 'styled-components'; // or emotion, pandacss, etc.\n\nfunction Example() {\n  return (\n    \u003cSheet\u003e\n      \u003cSheetContainer\u003e\n        \u003cSheetHeader /\u003e\n        \u003cSheetContent\u003e{/*...*/}\u003c/SheetContent\u003e\n      \u003c/SheetContainer\u003e\n      \u003cSheetBackdrop /\u003e\n    \u003c/Sheet\u003e\n  );\n}\n\nconst SheetContainer = styled(Sheet.Container)`\n  /* custom styles */\n`;\n\nconst SheetHeader = styled(Sheet.Header)`\n  /* custom styles */\n`;\n\nconst SheetContent = styled(Sheet.Content)`\n  /* custom styles */\n`;\n\nconst SheetBackdrop = styled(Sheet.Backdrop)`\n  /* custom styles */\n`;\n```\n\n### Custom styles example\n\nYou can customize the sheet quite a lot if you get creative with the styles.\n\nHere's an example how a totally custom sheet could look like:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"355\" height=\"768\" src=\"media/example-custom-styles.gif\"\u003e\n\u003c/p\u003e\n\nSee the [CustomStyles](https://github.com/Temzasse/react-modal-sheet/blob/main/example/src/components/CustomStyles.tsx) component to view the full implementation of the above example.\n\n\u003e [!IMPORTANT]\n\u003e Wrapping the sheet components with custom elements or components can have unexpected results since the default sheet styles provided by the library rely on the sheet parts being direct children of each other. Some behavious like keyboard avoidance or scroll handling might not work as expected if the sheet parts are not direct children of each other or if you customize the styles too much.\n\n## ♿️ Accessibility\n\nBy default, react-modal-sheet doesn't include any built-in accessibility properties in order to not make any assumptions and to support a wide range of use cases. Additionally, not including 3rd party libraries for features like focus trapping or screen reader support makes it possible to utilize any accessibility libraries that your project may already use, eg. [React Aria](https://react-spectrum.adobe.com/react-aria/getting-started.html). This also helps to reduce JS bloat by not including similar libraries multiple times in your app bundle.\n\nThe example below utilizes React Aria to achieve an accessible modal-like bottom sheet that can be closed via a button rendered inside a custom sheet header.\n\n\u003e ℹ️ The example was built by following the React Aria's [useDialog](https://react-spectrum.adobe.com/react-aria/useDialog.html) documentation.\n\n```tsx\nimport { Sheet } from 'react-modal-sheet';\nimport { useRef } from 'react';\nimport { useOverlayTriggerState } from 'react-stately';\n\nimport {\n  useOverlay,\n  useModal,\n  OverlayProvider,\n  FocusScope,\n  useButton,\n  useDialog,\n} from 'react-aria';\n\nfunction A11yExample() {\n  const sheetState = useOverlayTriggerState({});\n  const openButtonRef = useRef(null);\n  const openButton = useButton({ onPress: sheetState.open }, openButtonRef);\n\n  return (\n    \u003cdiv\u003e\n      \u003cbutton {...openButton.buttonProps} ref={openButtonRef}\u003e\n        Open sheet\n      \u003c/button\u003e\n\n      \u003cSheet isOpen={sheetState.isOpen} onClose={sheetState.close}\u003e\n        \u003cOverlayProvider\u003e\n          \u003cFocusScope contain autoFocus restoreFocus\u003e\n            \u003cSheetComp sheetState={sheetState} /\u003e\n          \u003c/FocusScope\u003e\n        \u003c/OverlayProvider\u003e\n      \u003c/Sheet\u003e\n    \u003c/div\u003e\n  );\n}\n\nfunction SheetComp({ sheetState }) {\n  const containerRef = useRef(null);\n  const dialog = useDialog({}, containerRef);\n  const overlay = useOverlay(\n    { onClose: sheetState.close, isOpen: true, isDismissable: true },\n    containerRef\n  );\n\n  const closeButtonRef = useRef(null);\n  const closeButton = useButton(\n    { onPress: sheetState.close, 'aria-label': 'Close sheet' },\n    closeButtonRef\n  );\n\n  useModal();\n\n  // In real world usage this would be a separate React component\n  const customHeader = (\n    \u003cdiv\u003e\n      \u003cspan {...dialog.titleProps}\u003eSome title for sheet\u003c/span\u003e\n      \u003cbutton {...closeButton.buttonProps}\u003e🅧\u003c/button\u003e\n    \u003c/div\u003e\n  );\n\n  return (\n    \u003c\u003e\n      \u003cSheet.Container\n        {...overlay.overlayProps}\n        {...dialog.dialogProps}\n        ref={containerRef}\n      \u003e\n        \u003cSheet.Header\u003e{customHeader}\u003c/Sheet.Header\u003e\n        \u003cSheet.Content\u003e{/*...*/}\u003c/Sheet.Content\u003e\n      \u003c/Sheet.Container\u003e\n      \u003cSheet.Backdrop /\u003e\n    \u003c/\u003e\n  );\n}\n```\n\nIf you want to see a more real-world-like implementation you can take a look at the [Slack example](example/components/slack-message/index.tsx) and try out the related [demo](https://temzasse.github.io/react-modal-sheet/#/slack-message) (optimized for mobile).\n\n### 🔩 Building a reusable sheet\n\nIn your projects it might make sense to build a reusable bottom sheet that has all the accessibility features included and can then be easily used in various places in the project. Take a look at the [A11ySheet](example/components/a11y/A11ySheet.tsx) example to get some insight on how to build such a component. By incorporating all the accessibility features inside your own reusable component you don't need to repeat them every time you want to use a bottom sheet in your app.\n\n## 🐛 Troubleshooting\n\n### The sheet doesn't open when using `StrictMode`\n\nIf you are using React `StrictMode` the sheet animations might not work as expected. This seems to be an issue in the `motion` library and I haven't been able to find a good solution for it yet. You can see all `motion` issues related to the `StrictMode` [here](https://github.com/motiondivision/motion/issues?q=is%3Aissue+StrictMode). Easiest solution is to just not use `StrictMode` 🤷‍♂️\n\n### Upgrading from v4 to v5\n\nIf you're experiencing issues after upgrading, check the \"What's new in v5\" section above for breaking changes. The most common issues are:\n\n1. **Snap points not working**: Ensure your snap points are in ascending order (`[0, 0.5, 1]` instead of `[1, 0.5, 0]`)\n2. **Scrolling issues**: Remove `Sheet.Scroller` components as scrolling is now handled by `Sheet.Content`\n3. **Detent errors**: Update detent prop values (`\"full-height\"` → `\"default\"`, `\"content-height\"` → `\"content\"`)\n\n## 🤝 Contributing\n\nIf you find a bug or have a feature request please open an issue. If you want to contribute code please open a pull request.\n\nYou can build and run the library and the example app locally like this:\n\n1. First install [yalc](https://github.com/wclr/yalc) globally for managing local package dependencies.\n\n```sh\nnpm i yalc -g\n```\n\n2. Install deps + configure yalc.\n\n```sh\nnpm install\nnpm run build\nnpm run link\n```\n\n3. Start a watcher for building the library.\n\n```sh\nnpm run dev\n```\n\n4. Run dev server for the example app (do this in another terminal).\n\n```sh\ncd example\nnpm run dev\n```\n\n5. Open http://localhost:5173/ to see the examples. Now if you make edits to the library code the example app will automatically refresh.\n\n\u003e [!TIP]\n\u003e If you want to test the example app on your phone you can run the example app with `npm run dev:host` and then open your computer's local IP address in your phone's browser. Make sure your phone is on the same network as your computer.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftemzasse%2Freact-modal-sheet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftemzasse%2Freact-modal-sheet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftemzasse%2Freact-modal-sheet/lists"}