{"id":13450369,"url":"https://github.com/wellyshen/react-cool-portal","last_synced_at":"2025-05-15T16:04:28.351Z","repository":{"id":37090708,"uuid":"241529345","full_name":"wellyshen/react-cool-portal","owner":"wellyshen","description":"😎 🍒 React hook for Portals, which renders modals, dropdowns, tooltips etc. to \u003cbody\u003e or else.","archived":false,"fork":false,"pushed_at":"2023-08-12T16:39:58.000Z","size":5890,"stargazers_count":738,"open_issues_count":24,"forks_count":23,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-06T08:23:30.418Z","etag":null,"topics":["dialog","dropdown","hook","lightbox","loading-bar","modal","notification","popover","portal","react","status-bar","toast","tooltip","typescript"],"latest_commit_sha":null,"homepage":"https://react-cool-portal.netlify.app","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/wellyshen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null},"funding":{"github":null,"patreon":null,"open_collective":"react-cool-portal","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2020-02-19T04:05:22.000Z","updated_at":"2025-04-25T06:23:54.000Z","dependencies_parsed_at":"2023-09-23T14:39:11.182Z","dependency_job_id":null,"html_url":"https://github.com/wellyshen/react-cool-portal","commit_stats":{"total_commits":907,"total_committers":6,"mean_commits":"151.16666666666666","dds":0.3914002205071665,"last_synced_commit":"1737a869b601866f6fc39948d773cb8ff76e2e7b"},"previous_names":[],"tags_count":58,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellyshen%2Freact-cool-portal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellyshen%2Freact-cool-portal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellyshen%2Freact-cool-portal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wellyshen%2Freact-cool-portal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wellyshen","download_url":"https://codeload.github.com/wellyshen/react-cool-portal/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254265751,"owners_count":22041980,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["dialog","dropdown","hook","lightbox","loading-bar","modal","notification","popover","portal","react","status-bar","toast","tooltip","typescript"],"created_at":"2024-07-31T07:00:34.039Z","updated_at":"2025-05-15T16:04:28.310Z","avatar_url":"https://github.com/wellyshen.png","language":"TypeScript","funding_links":["https://opencollective.com/react-cool-portal"],"categories":["Packages","TypeScript","Uncategorized"],"sub_categories":["Uncategorized"],"readme":"# \u003cem\u003e\u003cb\u003eREACT COOL PORTAL\u003c/b\u003e\u003c/em\u003e\n\nThis is a React [hook](https://reactjs.org/docs/hooks-custom.html#using-a-custom-hook) for [Portals](https://reactjs.org/docs/portals.html). It helps you render children into a DOM node that exists outside the DOM hierarchy of the parent component. From now on you will never need to struggle with modals, dropdowns, tooltips etc. Check the [features](#features) section out to learn more. Hope you guys 👍🏻 it.\n\n❤️ it? ⭐️ it on [GitHub](https://github.com/wellyshen/react-cool-portal/stargazers) or [Tweet](https://twitter.com/intent/tweet?text=With%20@react-cool-portal,%20I%20can%20build%20modals,%20dropdowns,%20tooltips%20etc.%20without%20struggle!%20Thanks,%20@Welly%20Shen%20🤩) about it.\n\n[![build status](https://img.shields.io/github/workflow/status/wellyshen/react-cool-portal/CI?style=flat-square)](https://github.com/wellyshen/react-cool-portal/actions?query=workflow%3ACI)\n[![coverage status](https://img.shields.io/coveralls/github/wellyshen/react-cool-portal?style=flat-square)](https://coveralls.io/github/wellyshen/react-cool-portal?branch=master)\n[![npm version](https://img.shields.io/npm/v/react-cool-portal?style=flat-square)](https://www.npmjs.com/package/react-cool-portal)\n[![npm downloads](https://img.shields.io/npm/dm/react-cool-portal?style=flat-square)](https://www.npmtrends.com/react-cool-portal)\n[![npm downloads](https://img.shields.io/npm/dt/react-cool-portal?style=flat-square)](https://www.npmtrends.com/react-cool-portal)\n[![gzip size](https://badgen.net/bundlephobia/minzip/react-cool-portal?label=gzip%20size\u0026style=flat-square)](https://bundlephobia.com/result?p=react-cool-portal)\n[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange?style=flat-square)](#contributors-)\n[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square)](CONTRIBUTING.md)\n[![Twitter URL](https://img.shields.io/twitter/url?style=social\u0026url=https%3A%2F%2Fgithub.com%2Fwellyshen%2Freact-cool-portal)](https://twitter.com/intent/tweet?text=With%20@react-cool-portal,%20I%20can%20build%20modals,%20dropdowns,%20tooltips%20etc.%20without%20struggle!%20Thanks,%20@Welly%20Shen%20🤩)\n\n## Live Demo\n\n![demo](https://user-images.githubusercontent.com/21308003/91049364-b1eb1580-e64f-11ea-9776-4668551db40d.gif)\n\n⚡️ Try yourself: https://react-cool-portal.netlify.app\n\n## Features\n\n- 🍒 Renders an element or component to `\u003cbody\u003e` or [a specified DOM element](#basic-use-case).\n- 🎣 React [Portals](https://reactjs.org/docs/portals.html) feat. [Hook](https://reactjs.org/docs/hooks-custom.html#using-a-custom-hook).\n- 🤖 Built-in [state controllers](#use-with-state), event listeners and many [useful features](#api) for a comprehensive DX.\n- 🧱 Used as a scaffold to [build your customized hook](#build-your-customized-hook).\n- 🧹 Auto removes the un-used portal container for you. Doesn't produce any DOM mess.\n- 📜 Supports [TypeScript](https://www.typescriptlang.org) type definition.\n- 🗄️ Server-side rendering compatibility.\n- 🦔 Tiny size ([~ 1KB gzipped](https://bundlephobia.com/result?p=react-cool-portal)). No external dependencies, aside for the `react` and `react-dom`.\n\n## Requirement\n\nTo use `react-cool-portal`, you must use `react@16.8.0` or greater which includes hooks.\n\n## Installation\n\nThis package is distributed via [npm](https://www.npmjs.com/package/react-cool-portal).\n\n```sh\n$ yarn add react-cool-portal\n# or\n$ npm install --save react-cool-portal\n```\n\n## Usage\n\nHere are some minimal examples of how does it work. You can learn more about it by checking the [API](#api) out.\n\n### Basic Use Case\n\nInserts an element or component into a different location in the DOM.\n\n```js\nimport usePortal from \"react-cool-portal\";\n\nconst App = () =\u003e {\n  const { Portal } = usePortal();\n\n  return (\n    \u003cdiv\u003e\n      \u003cPortal\u003e\n        \u003cp\u003e\n          Wow! I am rendered outside the DOM hierarchy of my parent component.\n        \u003c/p\u003e\n      \u003c/Portal\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\nBy default, the children of portal is rendered into `\u003cdiv id=\"react-cool-portal\"\u003e` of `\u003cbody\u003e`. You can specify the DOM element you want through the `containerId` option.\n\n```js\nimport usePortal from \"react-cool-portal\";\n\nconst App = () =\u003e {\n  const { Portal } = usePortal({ containerId: \"my-portal-root\" });\n\n  return (\n    \u003cdiv\u003e\n      \u003cPortal\u003e\n        \u003cp\u003eNow I am rendered into the specify element (id=\"my-portal-root\").\u003c/p\u003e\n      \u003c/Portal\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n\u003e Note: If the container element doesn't exist, we will create it for you.\n\n### Use with State\n\n`react-cool-portal` provides many useful features, which enable you to build a component with state. For instance, modal, dropdown, tooltip, and so on.\n\n[![Edit usePortal](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/useportal-v8voh?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport usePortal from \"react-cool-portal\";\n\nconst App = () =\u003e {\n  const { Portal, isShow, show, hide, toggle } = usePortal({\n    defaultShow: false, // The default visibility of portal, default is true\n    onShow: (e) =\u003e {\n      // Triggered when portal is shown\n      // The event object will be the parameter of `show(e?)`\n    },\n    onHide: (e) =\u003e {\n      // Triggered when portal is hidden\n      // The event object will be the parameter of `hide(e?)`, it maybe MouseEvent (on clicks outside) or KeyboardEvent (press ESC key)\n    },\n  });\n\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={show}\u003eOpen Modal\u003c/button\u003e\n      \u003cbutton onClick={hide}\u003eClose Modal\u003c/button\u003e\n      \u003cbutton onClick={toggle}\u003e{isShow ? \"Close\" : \"Open\"} Modal\u003c/button\u003e\n      \u003cPortal\u003e\n        \u003cdiv className=\"modal\" tabIndex={-1}\u003e\n          \u003cdiv\n            className=\"modal-dialog\"\n            role=\"dialog\"\n            aria-labelledby=\"modal-label\"\n            aria-modal=\"true\"\n          \u003e\n            \u003cdiv className=\"modal-header\"\u003e\n              \u003ch5 id=\"modal-label\" className=\"modal-title\"\u003e\n                Modal title\n              \u003c/h5\u003e\n            \u003c/div\u003e\n            \u003cdiv className=\"modal-body\"\u003e\n              \u003cp\u003eModal body text goes here.\u003c/p\u003e\n            \u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/Portal\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n\u003e 🧹 When no element in the container, we will remove it for you to avoid DOM mess. However, the feature can be turn off via the [autoRemoveContainer](#parameter-object-optional) option.\n\nThe above example shows how easy you can handle the visibility of your component. You may ask how to handle the visibility with animations? No worries, you can disable the built-in `show/hide` functions by setting the `internalShowHide` option to `false` then handling the visibility of your component via the `isShow` state.\n\n[![Edit usePortal with Animation](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/useportal-with-animation-eorc2?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nimport usePortal from \"react-cool-portal\";\n\nconst App = () =\u003e {\n  const { Portal, isShow, show, hide, toggle } = usePortal({\n    defaultShow: false,\n    internalShowHide: false, // Disable the built-in show/hide portal functions, default is true\n    onShow: (e) =\u003e {\n      // Triggered when `isShow` is set to true\n    },\n    onHide: (e) =\u003e {\n      // Triggered when `isShow` is set to false\n    },\n  });\n\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={show}\u003eOpen Modal\u003c/button\u003e\n      \u003cbutton onClick={hide}\u003eClose Modal\u003c/button\u003e\n      \u003cbutton onClick={toggle}\u003e{isShow ? \"Close\" : \"Open\"} Modal\u003c/button\u003e\n      \u003cPortal\u003e\n        \u003cdiv\n          // Now you can use the `isShow` state to handle the CSS animations\n          className={`modal${isShow ? \" modal-open\" : \"\"}`}\n          tabIndex={-1}\n        \u003e\n          \u003cdiv\n            className=\"modal-dialog\"\n            role=\"dialog\"\n            aria-labelledby=\"modal-label\"\n            aria-modal=\"true\"\n          \u003e\n            \u003cdiv className=\"modal-header\"\u003e\n              \u003ch5 id=\"modal-label\" className=\"modal-title\"\u003e\n                Modal title\n              \u003c/h5\u003e\n            \u003c/div\u003e\n            \u003cdiv className=\"modal-body\"\u003e\n              \u003cp\u003eModal body text goes here.\u003c/p\u003e\n            \u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/Portal\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\nBesides that, you can also handle the visibility of your component via React [animation events](https://reactjs.org/docs/events.html#animation-events) or [transition events](https://reactjs.org/docs/events.html#transition-events) like [what I did](app/src/App/index.tsx) for the [demo app](#live-demo).\n\n### Build Your Customized Hook\n\nAre you tired to write the same code over and over again? It's time to build your own hook based on `react-cool-portal` then use it wherever you want.\n\n```js\nimport { useCallback } from \"react\";\nimport usePortal from \"react-cool-portal\";\n\n// Customize your hook based on react-cool-portal\nconst useModal = (options = {}) =\u003e {\n  const { Portal, isShow, ...rest } = usePortal({\n    ...options,\n    defaultShow: false,\n    internalShowHide: false,\n  });\n\n  const Modal = useCallback(\n    ({ children }) =\u003e (\n      \u003cPortal\u003e\n        \u003cdiv className={`modal${isShow ? \" modal-open\" : \"\"}`} tabIndex={-1}\u003e\n          {children}\n        \u003c/div\u003e\n      \u003c/Portal\u003e\n    ),\n    [isShow]\n  );\n\n  return { Modal, isShow, ...rest };\n};\n\n// Use it wherever you want\nconst App = () =\u003e {\n  const { Modal, show, hide } = useModal();\n\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={show}\u003eOpen Modal\u003c/button\u003e\n      \u003cbutton onClick={hide}\u003eClose Modal\u003c/button\u003e\n      \u003cModal\u003e\n        \u003cdiv\n          className=\"modal-dialog\"\n          role=\"dialog\"\n          aria-labelledby=\"modal-label\"\n          aria-modal=\"true\"\n        \u003e\n          \u003cdiv className=\"modal-header\"\u003e\n            \u003ch5 id=\"modal-label\" className=\"modal-title\"\u003e\n              Modal title\n            \u003c/h5\u003e\n          \u003c/div\u003e\n          \u003cdiv className=\"modal-body\"\u003e\n            \u003cp\u003eModal body text goes here.\u003c/p\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/Modal\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\nOne problem of the above example is that CSS transition/animation will be cut off due to the re-creating of the `Portal` component. So if you want to apply transitions or animations to the wrapped element of the customized hook. The `isShow` need to be passed from the props.\n\n[![Edit usePortal - custom](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/useportal-custom-clnqz?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n```js\nconst useModal = (options = {}) =\u003e {\n  const { Portal, ...rest } = usePortal({\n    ...options,\n    defaultShow: false,\n    internalShowHide: false,\n  });\n\n  const Modal = useCallback(\n    // Pass the `isShow` from props to prevent CSS transition/animation to be cut off\n    ({ isShow, children }) =\u003e (\n      \u003cPortal\u003e\n        \u003cdiv className={`modal${isShow ? \" modal-open\" : \"\"}`} tabIndex={-1}\u003e\n          {children}\n        \u003c/div\u003e\n      \u003c/Portal\u003e\n    ),\n    []\n  );\n\n  return { Modal, ...rest };\n};\n```\n\n## Conditionally ESC or Click Outside to Hide\n\n**ESC or click outside to hide** is an out-of-box feature of `react-cool-portal`. However, you can conditionally hide the portal based on the presence or absence of an element. Here we take a nested modal as the example:\n\n```js\nimport usePortal from \"react-cool-portal\";\n\nconst App = () =\u003e {\n  const [showChildModal, setShowChildModal] = useState(false);\n  const { Portal: ChildModal, show } = usePortal({ defaultShow: false });\n  const { Portal: ParentModal } = usePortal({\n    // Provide the class name of the child modal, so the parent modal will only be hidden after the child modal is hidden\n    escToHide: [\"child-modal\"],\n    // The same as above\n    clickOutsideToHide: [\"child-modal\"],\n  });\n\n  return (\n    \u003cdiv\u003e\n      \u003cParentModal\u003e\n        \u003cdiv\u003e\n          \u003cp\u003eI'm parent modal.\u003c/p\u003e\n          \u003cbutton onClick={show}\u003eOpen Child Modal\u003c/button\u003e\n          \u003cChildModal\u003e\n            \u003cdiv className=\"child-modal\"\u003e\n              \u003cp\u003eI'm child modal.\u003c/p\u003e\n            \u003c/div\u003e\n          \u003c/ChildModal\u003e\n        \u003c/div\u003e\n      \u003c/ParentModal\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n## API\n\n```js\nconst returnObj = usePortal(parameterObj);\n```\n\n### Return object\n\nIt's returned with the following properties.\n\n| Key    | Type      | Default | Description                                                                                     |\n| ------ | --------- | ------- | ----------------------------------------------------------------------------------------------- |\n| Portal | component |         | Renders children into a DOM node that exists outside the DOM hierarchy of the parent component. |\n| isShow | boolean   | `false` | The show/hide state of portal.                                                                  |\n| show   | function  |         | To show the portal or set the `isShow` to `true`.                                               |\n| hide   | function  |         | To hide the portal or set the `isShow` to `false`.                                              |\n| toggle | function  |         | To toggle (show/hide) the portal or set the `isShow` to `true/false`.                           |\n\n### Parameter object (optional)\n\nWhen use `react-cool-portal` you can configure the following options via the parameter.\n\n| Key                 | Type             | Default             | Description                                                                                                                                          |\n| ------------------- | ---------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |\n| containerId         | string           | `react-cool-portal` | You can specify your own container id from an existing DOM element or let this hook automatically creates it for you.                                |\n| autoRemoveContainer | boolean          | `true`              | Enable/disable the built-in automatically remove container function.                                                                                 |\n| defaultShow         | boolean          | `true`              | The initial show/hide state of the portal.                                                                                                           |\n| clickOutsideToHide  | boolean \\| array | `true`              | Hide the portal by clicking outside of it. You can also provide class name(s) for [conditionally hide](#conditionally-esc-or-click-outside-to-hide). |\n| escToHide           | boolean \\| array | `true`              | Hide the portal by pressing ESC key. You can also provide class name(s) for [conditionally hide](#conditionally-esc-or-click-outside-to-hide).       |\n| internalShowHide    | boolean          | `true`              | Enable/disable the built-in `show/hide` portal functions, which gives you a flexible way to handle your portal.                                      |\n| onShow              | function         |                     | Triggered when portal is shown or the `isShow` set to `true`.                                                                                        |\n| onHide              | function         |                     | Triggered when portal is hidden or the `isShow` set to `false`.                                                                                      |\n\n## Articles / Blog Posts\n\n\u003e 💡 If you have written any blog post or article about `react-cool-portal`, please open a PR to add it here.\n\n- Featured on [React Newsletter #206](https://reactnewsletter.com/issues/206).\n\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://wellyshen.com\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/21308003?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eWelly\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/wellyshen/react-cool-portal/commits?author=wellyshen\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/wellyshen/react-cool-portal/commits?author=wellyshen\" title=\"Documentation\"\u003e📖\u003c/a\u003e \u003ca href=\"#maintenance-wellyshen\" title=\"Maintenance\"\u003e🚧\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/hinok\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/1313605?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eDawid Karabin\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/wellyshen/react-cool-portal/commits?author=hinok\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"http://janstepanovsky.cz\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/854103?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eHonza Stepanovsky\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/wellyshen/react-cool-portal/issues?q=author%3Ahhhonzik\" title=\"Bug reports\"\u003e🐛\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-restore --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwellyshen%2Freact-cool-portal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwellyshen%2Freact-cool-portal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwellyshen%2Freact-cool-portal/lists"}