UNPKG

13.1 kBMarkdownView Raw
1<div align="left">
2 <h1 align="center">REACT FOCUS LOCK</h1>
3 <img src="./assets/ackbar.png" alt="it-is-a-trap" width="200" height="200" align="right">
4
5 - browser friendly focus lock<br/>
6 - matching all your use cases<br/>
7 - trusted by best UI frameworks<br/>
8 - the thing Admiral Ackbar was talking about<br/>
9 <br/>
10
11[![CircleCI status](https://img.shields.io/circleci/project/github/theKashey/react-focus-lock/master.svg?style=flat-square)](https://circleci.com/gh/theKashey/react-focus-lock/tree/master)
12[![npm](https://img.shields.io/npm/v/react-focus-lock.svg)](https://www.npmjs.com/package/react-focus-lock)
13[![bundle size](https://badgen.net/bundlephobia/minzip/react-focus-lock)](https://bundlephobia.com/result?p=react-focus-lock)
14[![downloads](https://badgen.net/npm/dm/react-focus-lock)](https://www.npmtrends.com/react-focus-lock)
15 <hr/>
16</div>
17
18It is a trap! We got your focus and will not let him out!
19
20- Modal dialogs. You can not leave it with "Tab", ie do a "tab-out".
21- Focused tasks. It will aways brings you back, as you can "lock" user inside a component.
22- Any any other case, when you have to lock user _intention_ and _focus_, if that's what `a11y` is asking for.
23
24### Trusted
25Trusted by
26[Atlassian AtlasKit](https://atlaskit.atlassian.com),
27[ReachUI](https://ui.reach.tech/),
28[SmoothUI](https://smooth-ui.smooth-code.com/),
29[Storybook](https://storybook.js.org/)
30and we will do our best to earn your trust too!
31
32# Features
33 - no keyboard control, everything is done watching a __focus behavior__, not emulating it. Thus works always and everywhere.
34 - React __Portals__ support. Even if some data is in outer space - it is [still in lock](https://github.com/theKashey/react-focus-lock/issues/19).
35 - _Scattered_ locks, or focus lock groups - you can setup different isolated locks, and _tab_ from one to another.
36 - Controllable isolation level.
37 - variable size bundle. Uses sidecar to trim UI part to 1.5kb.
38
39> 💡 __focus__ locks is only the first part, there are also __scroll lock__ and __text-to-speech__ lock
40you have to use to really "lock" the user.
41Try [react-focus-on](https://github.com/theKashey/react-focus-on) to archive everything above, assembled in the right order.
42
43# How to use
44Just wrap something with focus lock, and focus will be `moved inside` on mount.
45```js
46 import FocusLock from 'react-focus-lock';
47
48 const JailForAFocus = ({onClose}) => (
49 <FocusLock>
50 You can not leave this form
51 <button onClick={onClose} />
52 </FocusLock>
53 );
54```
55Demo - https://codesandbox.io/s/5wmrwlvxv4.
56
57# WHY?
58From [MDN Article about accessible dialogs](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_dialog_role):
59 - The dialog must be properly labeled
60 - Keyboard __focus must be managed__ correctly
61
62This one is about managing the focus.
63
64I've got a good [article about focus management, dialogs and WAI-ARIA](https://medium.com/@antonkorzunov/its-a-focus-trap-699a04d66fb5).
65
66# API
67> FocusLock would work perfectly even with no props set.
68
69 FocusLock has few props to tune behavior, all props are optional:
70 - `disabled`, to disable(enable) behavior without altering the tree.
71 - `className`, to set the `className` of the internal wrapper.
72 - `returnFocus`, to return focus into initial position on unmount(not disable).
73> By default `returnFocus` is disabled, so FocusLock will __not__ restore original focus on deactivation.
74
75 This is expected behavior for Modals, but it is better to implement it by your self. See [unmounting and focus management](https://github.com/theKashey/react-focus-lock#unmounting-and-focus-management) for details
76 - `persistentFocus=false`, requires any element to be focused. This also disables text selections inside, and __outside__ focus lock.
77 - `autoFocus=true`, enables or disables focusing into on Lock activation. If disabled Lock will blur an active focus.
78 - `noFocusGuards=false` disabled _focus guards_ - virtual inputs which secure tab index.
79 - `group='''` named focus group for focus scattering aka [combined lock targets](https://github.com/theKashey/vue-focus-lock/issues/2)
80 - `shards=[]` an array of `ref` pointing to the nodes, which focus lock should consider and a part of it. This is another way focus scattering.
81 - `whiteList=fn` you could _whitelist_ locations FocusLock should carry about. Everything outside it will ignore. For example - any modals.
82 - `as='div'` if you need to change internal `div` element, to any other. Use ref forwarding to give FocusLock the node to work with.
83 - `lockProps={}` to pass any extra props (except className) to the internal wrapper.
84 - `hasPositiveIndices=false` to support a focus lock behavior when any elements tabIndex greater than 0.
85 - `crossFrame=true` enables aggressive focus capturing within iframes
86
87### Focusing in OSX (Safari/Firefox) is strange!
88By default `tabbing` in OSX `sees` only controls, but not links or anything else `tabbable`. This is system settings, and Safari/Firefox obey.
89Press Option+Tab in Safari to loop across all tabbables, or change the Safari settings. There is no way to _fix_ Firefox, unless change system settings (Control+F7). See [this issue](https://github.com/theKashey/react-focus-lock/issues/24) for more information.
90
91## Set up
92### Requirements
93- version 1x is React 15/16 compatible
94- version 2+ requires React 16.8+ (hooks)
95### Import
96`react-focus-lock` exposed __3 entry points__: for the classical usage, and a _sidecar_ one.
97#### Default usage
98- 4kb, `import FocusLock from 'react-focus-lock` would give you component you are looking for.
99
100#### Separated usage
101Meanwhile - you dont need any focus related logic until it's needed.
102Thus - you may defer that logic till Lock activation and move all related code to a _sidecar_.
103
104- UI, __1.5kb__, `import FocusLockUI from 'react-focus-lock/UI` - a DOM part of a lock.
105- Sidecar, 3.5kb, `import Sidecar from 'react-focus-lock/sidecar` - which is the real focus lock.
106
107```js
108import FocusLockUI from "react-focus-lock/UI";
109import {sidecar} from "use-sidecar";
110
111// prefetch sidecar. data would be loaded, but js would not be executed
112const FocusLockSidecar = sidecar(
113 () => import(/* webpackPrefetch: true */ "react-focus-lock/sidecar")
114);
115
116<FocusLockUI
117 disabled={this.state.disabled}
118 sideCar={FocusLockSidecar}
119>
120 {content}
121</FocusLockUI>
122```
123That would split FocusLock into two pieces, reducing app size and improving the first load.
124The cost of focus-lock is just 1.5kb!
125
126> Saved 3.5kb?! 🤷‍♂️ 3.5kb here and 3.5kb here, and your 20mb bundle is ready.
127
128# Autofocus
129 Use when you cannot use the native `autoFocus` prop - because you only want to autofocus once the Trap has been activated
130
131 - prop `data-autofocus` on the element.
132 - prop `data-autofocus-inside` on the element to focus on something inside.
133 - `AutoFocusInside` component, as named export of this library.
134```js
135 import FocusLock, { AutoFocusInside } from 'react-focus-lock';
136
137 <FocusLock>
138 <button>Click</button>
139 <AutoFocusInside>
140 <button>will be focused</button>
141 </AutoFocusInside>
142 </FocusLock>
143 // is the same as
144
145 <FocusLock>
146 <button>Click</button>
147 <button data-autofocus>will be focused</button>
148 </FocusLock>
149 ```
150
151 If there is more than one auto-focusable target - the first will be selected.
152 If it is a part of radio group, and __rest of radio group element are also autofocusable__(just put them into AutoFocusInside) -
153 checked one fill be selected.
154
155 `AutoFocusInside` will work only on Lock activation, and does nothing, then used outside of the lock.
156 You can use `MoveFocusInside` to move focus inside with or without lock.
157
158```js
159 import { MoveFocusInside } from 'react-focus-lock';
160
161 <MoveFocusInside>
162 <button>will be focused</button>
163 </MoveFocusInside>
164 ```
165
166# Portals
167Use focus scattering to handle portals
168
169- using `groups`. Just create a few locks (only one could be active) with a same group name
170```js
171const PortaledElement = () => (
172 <FocusLock group="group42" disabled={true}>
173 // "discoverable" portaled content
174 </FocusLock>
175);
176
177<FocusLock group="group42">
178 // main content
179</FocusLock>
180```
181- using `shards`. Just pass all the pieces to the "shards" prop.
182```js
183const PortaledElement = () => (
184 <div ref={ref}>
185 // "discoverable" portaled content
186 </div>
187);
188
189<FocusLock shards={[ref]}>
190 // main content
191</FocusLock>
192```
193- without anything. FocusLock will not prevent focusing portaled element, but will not include them in to tab order
194```js
195const PortaledElement = () => (
196 <div>
197 // NON-"discoverable" portaled content
198 </div>
199);
200
201<FocusLock shards={[ref]}>
202 // main content
203 <PortaledElement />
204</FocusLock>
205```
206
207### Using your own `Components`
208You may use `as` prop to change _what_ Focus-Lock will render around `children`.
209```js
210<FocusLock as="section">
211 <button>Click</button>
212 <button data-autofocus>will be focused</button>
213 </FocusLock>
214
215 <FocusLock as={AnotherComponent} lockProps={{anyAnotherComponentProp: 4}}>
216 <button>Click</button>
217 <span>Hello there!</span>
218 </FocusLock>
219```
220
221### Guarding
222As you may know - FocusLock is adding `Focus Guards` before and after lock to remove some side effects, like page scrolling.
223But `shards` will not have such guards, and it might be not so cool to use them - for example if no `tabbable` would be
224defined after shard - you will tab to the browser chrome.
225
226You may wrap shard with `InFocusGuard` or just drop `InFocusGuard` here and there - that would solve the problem.
227```js
228import {InFocusGuard} from 'react-focus-lock';
229
230// wrap with
231<InFocusGuard>
232 <button />
233</InFocusGuard>
234
235// place before and after
236<InFocusGuard />
237<button />
238<InFocusGuard />
239```
240InFocusGuards would be active(tabbable) only when tabble, it protecting, is focused.
241
242#### Removing Tailing Guard
243If only your modal is the last tabble element on the body - you might remove the Tailing Guard,
244to allow user _tab_ into address bar.
245```js
246<InFocusGuard/>
247<button />
248// there is no "tailing" guard :)
249```
250
251# Unmounting and focus management
252 - In case FocusLock has `returnFocus` enabled, and it's going to be unmounted - focus will be returned after zero-timeout.
253 - In case `returnFocus` is set to `false`, and you are going to control focus change on your own - keep in mind
254 >> React will first call Parent.componentWillUnmount, and next Child.componentWillUnmount
255
256 This means - Trap will be still active by the time you _may_ want move(return) focus on componentWillUnmount. Please deffer this action with a zero-timeout.
257
258 Similarly, if you are using the `disabled` prop to control FocusLock, you will need a zero-timeout to correctly restore focus.
259
260```
261<FocusLock
262 disabled={isFocusLockDisabled}
263 onDeactivation={() => {
264 // Without the zero-timeout, focus will likely remain on the button/control
265 // you used to set isFocusLockDisabled = true
266 window.setTimeout(() => myRef.current.focus(), 0);
267 }
268>
269```
270
271## Return focus with no scroll
272> read more at the [issue #83](https://github.com/theKashey/react-focus-lock/issues/83) or
273[mdn article](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus).
274
275To return focus, but without _jumpy_ page scroll returning a focus you might specify a focus option
276```js
277<FocusLock
278 returnFocus={{ preventScroll: false }} // working not in all browsers
279>
280```
281Not supported by Edge and Safari.
282
283# Not only for React
284 Uses [focus-lock](https://github.com/theKashey/focus-lock/) under the hood. It does also provide support for Vue.js and Vanilla DOM solutions
285
286# Warning!
287Two different _focus-lock-managers_ or even different version of a single one, active
288simultaneously will FIGHT!
289
290__Focus-lock will surrender__, as long any other focus management library will not.
291
292## Focus fighting
293You may wrap some render branch with `FreeFocusInside`, and react-focus-lock __will ignore__
294any focus inside marked node, thus landing a peace.
295
296```js
297import { FreeFocusInside } from 'react-focus-lock';
298
299<FreeFocusInside>
300 <div id="portal-for-modals">
301 in this div i am going to portal my modals, dont fight with them please
302 </div>
303</FreeFocusInside>
304```
305
306Even the better is to `whiteList` FocusLock areas - for example "you should handle only React Stuff in React Root"
307```js
308<FocusLock whiteList={node => document.getElementById('root').contains(node)}>
309 ...
310</FocusLock>
311```
312
313PS: __please use webpack or yarn resolution for force one version of react-focus-lock used__
314
315> webpack.conf
316```js
317 resolve: {
318 alias: {
319 'react-focus-lock': path.resolve(path.join(__dirname, './node_modules/react-focus-lock'))
320 ...
321```
322
323# More
324To create a "right" modal dialog you have to:
325- manage a focus. Use this library
326- block document scroll. Use [react-scroll-locky](https://github.com/theKashey/react-scroll-locky).
327- hide everything else from screen readers. Use [aria-hidden](https://github.com/theKashey/aria-hidden)
328
329You may use [react-focus-on](https://github.com/theKashey/react-focus-on) to achieve everything above, assembled in the right order.
330
331# Licence
332 MIT
333
334