diff --git a/docs/pages/api-docs/unstable-trap-focus.md b/docs/pages/api-docs/unstable-trap-focus.md
index 095e8bd63429b3..d1622bd14f40e3 100644
--- a/docs/pages/api-docs/unstable-trap-focus.md
+++ b/docs/pages/api-docs/unstable-trap-focus.md
@@ -37,3 +37,7 @@ Utility component that locks focus inside the component.
The component cannot hold a ref.
+## Demos
+
+- [Trap Focus](/components/trap-focus/)
+
diff --git a/docs/pages/components/trap-focus.js b/docs/pages/components/trap-focus.js
new file mode 100644
index 00000000000000..f26ed07bbadda2
--- /dev/null
+++ b/docs/pages/components/trap-focus.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';
+
+const pageFilename = 'components/trap-focus';
+const requireDemo = require.context('docs/src/pages/components/trap-focus', false, /\.(js|tsx)$/);
+const requireRaw = require.context(
+ '!raw-loader!../../src/pages/components/trap-focus',
+ false,
+ /\.(js|md|tsx)$/,
+);
+
+export default function Page({ demos, docs }) {
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
+ return { demos, docs };
+};
diff --git a/docs/src/pages.js b/docs/src/pages.js
index aa9f3c94278f81..bdf0d1533c6060 100644
--- a/docs/src/pages.js
+++ b/docs/src/pages.js
@@ -124,6 +124,7 @@ const pages = [
{ pathname: '/components/speed-dial' },
{ pathname: '/components/timeline' },
{ pathname: '/components/toggle-button' },
+ { pathname: '/components/trap-focus' },
{ pathname: '/components/tree-view' },
],
},
diff --git a/docs/src/pages/components/trap-focus/BasicTrapFocus.js b/docs/src/pages/components/trap-focus/BasicTrapFocus.js
new file mode 100644
index 00000000000000..127d8d326f3cc0
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/BasicTrapFocus.js
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import TrapFocus from '@material-ui/core/Unstable_TrapFocus';
+
+export default function BasicTrapFocus() {
+ const [open, setOpen] = React.useState(false);
+ return (
+
+ );
+}
diff --git a/docs/src/pages/components/trap-focus/BasicTrapFocus.tsx b/docs/src/pages/components/trap-focus/BasicTrapFocus.tsx
new file mode 100644
index 00000000000000..127d8d326f3cc0
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/BasicTrapFocus.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import TrapFocus from '@material-ui/core/Unstable_TrapFocus';
+
+export default function BasicTrapFocus() {
+ const [open, setOpen] = React.useState(false);
+ return (
+
+ );
+}
diff --git a/docs/src/pages/components/trap-focus/DisableEnforceFocus.js b/docs/src/pages/components/trap-focus/DisableEnforceFocus.js
new file mode 100644
index 00000000000000..8c94a48a8a318d
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/DisableEnforceFocus.js
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import TrapFocus from '@material-ui/core/Unstable_TrapFocus';
+
+export default function DisableEnforceFocus() {
+ const [open, setOpen] = React.useState(false);
+ return (
+
+ );
+}
diff --git a/docs/src/pages/components/trap-focus/DisableEnforceFocus.tsx b/docs/src/pages/components/trap-focus/DisableEnforceFocus.tsx
new file mode 100644
index 00000000000000..8c94a48a8a318d
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/DisableEnforceFocus.tsx
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import TrapFocus from '@material-ui/core/Unstable_TrapFocus';
+
+export default function DisableEnforceFocus() {
+ const [open, setOpen] = React.useState(false);
+ return (
+
+ );
+}
diff --git a/docs/src/pages/components/trap-focus/DisableRestoreFocus.js b/docs/src/pages/components/trap-focus/DisableRestoreFocus.js
new file mode 100644
index 00000000000000..ab5806f314f07d
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/DisableRestoreFocus.js
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import TrapFocus from '@material-ui/core/Unstable_TrapFocus';
+
+export default function DisableRestoreFocus() {
+ const [open, setOpen] = React.useState(false);
+ return (
+
+ );
+}
diff --git a/docs/src/pages/components/trap-focus/DisableRestoreFocus.tsx b/docs/src/pages/components/trap-focus/DisableRestoreFocus.tsx
new file mode 100644
index 00000000000000..ab5806f314f07d
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/DisableRestoreFocus.tsx
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import TrapFocus from '@material-ui/core/Unstable_TrapFocus';
+
+export default function DisableRestoreFocus() {
+ const [open, setOpen] = React.useState(false);
+ return (
+
+ );
+}
diff --git a/docs/src/pages/components/trap-focus/LazyTrapFocus.js b/docs/src/pages/components/trap-focus/LazyTrapFocus.js
new file mode 100644
index 00000000000000..44be0a2418cb37
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/LazyTrapFocus.js
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import TrapFocus from '@material-ui/core/Unstable_TrapFocus';
+
+export default function LazyTrapFocus() {
+ const [open, setOpen] = React.useState(false);
+ return (
+
+ );
+}
diff --git a/docs/src/pages/components/trap-focus/LazyTrapFocus.tsx b/docs/src/pages/components/trap-focus/LazyTrapFocus.tsx
new file mode 100644
index 00000000000000..44be0a2418cb37
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/LazyTrapFocus.tsx
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import TrapFocus from '@material-ui/core/Unstable_TrapFocus';
+
+export default function LazyTrapFocus() {
+ const [open, setOpen] = React.useState(false);
+ return (
+
+ );
+}
diff --git a/docs/src/pages/components/trap-focus/PortalTrapFocus.js b/docs/src/pages/components/trap-focus/PortalTrapFocus.js
new file mode 100644
index 00000000000000..910d39b1c61ad7
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/PortalTrapFocus.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import Portal from '@material-ui/core/Portal';
+import TrapFocus from '@material-ui/core/Unstable_TrapFocus';
+
+export default function PortalTrapFocus() {
+ const [open, setOpen] = React.useState(false);
+ const [container, setContainer] = React.useState(null);
+
+ return (
+
+
setOpen(true)}>
+ Open
+
+
+ {open && (
+
true} getDoc={() => document}>
+
+
+ )}
+
+
+
+ );
+}
diff --git a/docs/src/pages/components/trap-focus/PortalTrapFocus.tsx b/docs/src/pages/components/trap-focus/PortalTrapFocus.tsx
new file mode 100644
index 00000000000000..7394e5e6556295
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/PortalTrapFocus.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import Portal from '@material-ui/core/Portal';
+import TrapFocus from '@material-ui/core/Unstable_TrapFocus';
+
+export default function PortalTrapFocus() {
+ const [open, setOpen] = React.useState(false);
+ const [container, setContainer] = React.useState(null);
+
+ return (
+
+
setOpen(true)}>
+ Open
+
+
+ {open && (
+
true} getDoc={() => document}>
+
+
+ )}
+
+
+ );
+}
diff --git a/docs/src/pages/components/trap-focus/trap-focus.md b/docs/src/pages/components/trap-focus/trap-focus.md
new file mode 100644
index 00000000000000..da78eb083a92c6
--- /dev/null
+++ b/docs/src/pages/components/trap-focus/trap-focus.md
@@ -0,0 +1,50 @@
+---
+title: Trap Focus React component
+components: Unstable_TrapFocus
+---
+
+# Trap Focus
+
+Trap focus within a DOM node.
+
+`TrapFocus` is a utility component that manage focus for its descendants.
+This is useful when implementing overlays like modal dialogs, which should not allow focus to escape them while open.
+
+When `open={true}` the trap enables, pressing Tab or Shift+Tab will circle focus within the inner focusable elements of the component.
+
+- 📦 [1.5 kB gzipped](https://material-ui.com/size-snapshot).
+- ⚛️ Support portals
+
+## Example
+
+{{"demo": "pages/components/trap-focus/BasicTrapFocus.js"}}
+
+## Disable enforce focus
+
+Clicks within the focus trap behave normally; but clicks outside the focus trap are blocked.
+
+You can disable the behavior with the `disableEnforceFocus` prop.
+
+{{"demo": "pages/components/trap-focus/DisableEnforceFocus.js"}}
+
+## Disable restore focus
+
+The component restores the focus back to the previously focused element when it deactivates, for example, back to a button which opened a dialog.
+
+You can disable this behavior with the `disableRestoreFocus` prop.
+
+{{"demo": "pages/components/trap-focus/DisableRestoreFocus.js"}}
+
+## Lazy activation
+
+By default, the component moves the focus to its descendants as soon as it opens, `open={true}`.
+
+You can disale this behavior, and make it lazy, with the `disableAutoFocus` prop.
+
+{{"demo": "pages/components/trap-focus/LazyTrapFocus.js"}}
+
+## Portal
+
+The following demo uses [`Portal`](/components/portal/) to render a part of the trap focus into a new "subtree" outside of current DOM hierarchy.
+
+{{"demo": "pages/components/trap-focus/PortalTrapFocus.js"}}
diff --git a/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.d.ts b/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.d.ts
index 4b5c65287cafa6..be232c4777cf90 100644
--- a/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.d.ts
+++ b/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.d.ts
@@ -45,6 +45,10 @@ export interface TrapFocusProps {
/**
* Utility component that locks focus inside the component.
+ * Demos:
+ *
+ * - [Trap Focus](https://material-ui.com/components/trap-focus/)
+ *
* API:
*
* - [Unstable_TrapFocus API](https://material-ui.com/api/unstable-trap-focus/)