UNPKG

13 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
86### Focusing in OSX (Safari/Firefox) is strange!
87By default `tabbing` in OSX `sees` only controls, but not links or anything else `tabbable`. This is system settings, and Safari/Firefox obey.
88Press 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.
89
90## Set up
91### Requirements
92- version 1x is React 15/16 compatible
93- version 2+ requires React 16.8+ (hooks)
94### Import
95`react-focus-lock` exposed __3 entry points__: for the classical usage, and a _sidecar_ one.
96#### Default usage
97- 4kb, `import FocusLock from 'react-focus-lock` would give you component you are looking for.
98
99#### Separated usage
100Meanwhile - you dont need any focus related logic until it's needed.
101Thus - you may defer that logic till Lock activation and move all related code to a _sidecar_.
102
103- UI, __1.5kb__, `import FocusLockUI from 'react-focus-lock/UI` - a DOM part of a lock.
104- Sidecar, 3.5kb, `import Sidecar from 'react-focus-lock/sidecar` - which is the real focus lock.
105
106```js
107import FocusLockUI from "react-focus-lock/UI";
108import {sidecar} from "use-sidecar";
109
110// prefetch sidecar. data would be loaded, but js would not be executed
111const FocusLockSidecar = sidecar(
112 () => import(/* webpackPrefetch: true */ "react-focus-lock/sidecar")
113);
114
115<FocusLockUI
116 disabled={this.state.disabled}
117 sideCar={FocusLockSidecar}
118>
119 {content}
120</FocusLockUI>
121```
122That would split FocusLock into two pieces, reducing app size and improving the first load.
123The cost of focus-lock is just 1.5kb!
124
125> Saved 3.5kb?! 🤷‍♂️ 3.5kb here and 3.5kb here, and your 20mb bundle is ready.
126
127# Autofocus
128 Use when you cannot use the native `autoFocus` prop - because you only want to autofocus once the Trap has been activated
129
130 - prop `data-autofocus` on the element.
131 - prop `data-autofocus-inside` on the element to focus on something inside.
132 - `AutoFocusInside` component, as named export of this library.
133```js
134 import FocusLock, { AutoFocusInside } from 'react-focus-lock';
135
136 <FocusLock>
137 <button>Click</button>
138 <AutoFocusInside>
139 <button>will be focused</button>
140 </AutoFocusInside>
141 </FocusLock>
142 // is the same as
143
144 <FocusLock>
145 <button>Click</button>
146 <button data-autofocus>will be focused</button>
147 </FocusLock>
148 ```
149
150 If there is more than one auto-focusable target - the first will be selected.
151 If it is a part of radio group, and __rest of radio group element are also autofocusable__(just put them into AutoFocusInside) -
152 checked one fill be selected.
153
154 `AutoFocusInside` will work only on Lock activation, and does nothing, then used outside of the lock.
155 You can use `MoveFocusInside` to move focus inside with or without lock.
156
157```js
158 import { MoveFocusInside } from 'react-focus-lock';
159
160 <MoveFocusInside>
161 <button>will be focused</button>
162 </MoveFocusInside>
163 ```
164
165# Portals
166Use focus scattering to handle portals
167
168- using `groups`. Just create a few locks (only one could be active) with a same group name
169```js
170const PortaledElement = () => (
171 <FocusLock group="group42" disabled={true}>
172 // "discoverable" portaled content
173 </FocusLock>
174);
175
176<FocusLock group="group42">
177 // main content
178</FocusLock>
179```
180- using `shards`. Just pass all the pieces to the "shards" prop.
181```js
182const PortaledElement = () => (
183 <div ref={ref}>
184 // "discoverable" portaled content
185 </div>
186);
187
188<FocusLock shards={[ref]}>
189 // main content
190</FocusLock>
191```
192- without anything. FocusLock will not prevent focusing portaled element, but will not include them in to tab order
193```js
194const PortaledElement = () => (
195 <div>
196 // NON-"discoverable" portaled content
197 </div>
198);
199
200<FocusLock shards={[ref]}>
201 // main content
202 <PortaledElement />
203</FocusLock>
204```
205
206### Using your own `Components`
207You may use `as` prop to change _what_ Focus-Lock will render around `children`.
208```js
209<FocusLock as="section">
210 <button>Click</button>
211 <button data-autofocus>will be focused</button>
212 </FocusLock>
213
214 <FocusLock as={AnotherComponent} lockProps={{anyAnotherComponentProp: 4}}>
215 <button>Click</button>
216 <span>Hello there!</span>
217 </FocusLock>
218```
219
220### Guarding
221As you may know - FocusLock is adding `Focus Guards` before and after lock to remove some side effects, like page scrolling.
222But `shards` will not have such guards, and it might be not so cool to use them - for example if no `tabbable` would be
223defined after shard - you will tab to the browser chrome.
224
225You may wrap shard with `InFocusGuard` or just drop `InFocusGuard` here and there - that would solve the problem.
226```js
227import {InFocusGuard} from 'react-focus-lock';
228
229// wrap with
230<InFocusGuard>
231 <button />
232</InFocusGuard>
233
234// place before and after
235<InFocusGuard />
236<button />
237<InFocusGuard />
238```
239InFocusGuards would be active(tabbable) only when tabble, it protecting, is focused.
240
241#### Removing Tailing Guard
242If only your modal is the last tabble element on the body - you might remove the Tailing Guard,
243to allow user _tab_ into address bar.
244```js
245<InFocusGuard/>
246<button />
247// there is no "tailing" guard :)
248```
249
250# Unmounting and focus management
251 - In case FocusLock has `returnFocus` enabled, and it's going to be unmounted - focus will be returned after zero-timeout.
252 - In case `returnFocus` is set to `false`, and you are going to control focus change on your own - keep in mind
253 >> React will first call Parent.componentWillUnmount, and next Child.componentWillUnmount
254
255 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.
256
257 Similarly, if you are using the `disabled` prop to control FocusLock, you will need a zero-timeout to correctly restore focus.
258
259```
260<FocusLock
261 disabled={isFocusLockDisabled}
262 onDeactivation={() => {
263 // Without the zero-timeout, focus will likely remain on the button/control
264 // you used to set isFocusLockDisabled = true
265 window.setTimeout(() => myRef.current.focus(), 0);
266 }
267>
268```
269
270## Return focus with no scroll
271> read more at the [issue #83](https://github.com/theKashey/react-focus-lock/issues/83) or
272[mdn article](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus).
273
274To return focus, but without _jumpy_ page scroll returning a focus you might specify a focus option
275```js
276<FocusLock
277 returnFocus={{ preventScroll: false }} // working not in all browsers
278>
279```
280Not supported by Edge and Safari.
281
282# Not only for React
283 Uses [focus-lock](https://github.com/theKashey/focus-lock/) under the hood. It does also provide support for Vue.js and Vanilla DOM solutions
284
285# Warning!
286Two different _focus-lock-managers_ or even different version of a single one, active
287simultaneously will FIGHT!
288
289__Focus-lock will surrender__, as long any other focus management library will not.
290
291## Focus fighting
292You may wrap some render branch with `FreeFocusInside`, and react-focus-lock __will ignore__
293any focus inside marked node, thus landing a peace.
294
295```js
296import { FreeFocusInside } from 'react-focus-lock';
297
298<FreeFocusInside>
299 <div id="portal-for-modals">
300 in this div i am going to portal my modals, dont fight with them please
301 </div>
302</FreeFocusInside>
303```
304
305Even the better is to `whiteList` FocusLock areas - for example "you should handle only React Stuff in React Root"
306```js
307<FocusLock whiteList={node => document.getElementById('root').contains(node)}>
308 ...
309</FocusLock>
310```
311
312PS: __please use webpack or yarn resolution for force one version of react-focus-lock used__
313
314> webpack.conf
315```js
316 resolve: {
317 alias: {
318 'react-focus-lock': path.resolve(path.join(__dirname, './node_modules/react-focus-lock'))
319 ...
320```
321
322# More
323To create a "right" modal dialog you have to:
324- manage a focus. Use this library
325- block document scroll. Use [react-scroll-locky](https://github.com/theKashey/react-scroll-locky).
326- hide everything else from screen readers. Use [aria-hidden](https://github.com/theKashey/aria-hidden)
327
328You may use [react-focus-on](https://github.com/theKashey/react-focus-on) to achieve everything above, assembled in the right order.
329
330# Licence
331 MIT
332
333