UNPKG

12.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[![Greenkeeper badge](https://badges.greenkeeper.io/theKashey/react-focus-lock.svg)](https://greenkeeper.io/)
16 <hr/>
17</div>
18
19It is a trap! We got your focus and will not let him out!
20
21- Modal dialogs. You can not leave it with "Tab", ie do a "tab-out".
22- Focused tasks. It will aways brings you back, as you can "lock" user inside a component.
23- Any any other case, when you have to lock user _intention_ and _focus_, if that's what `a11y` is asking for.
24
25### Trusted
26Trusted by
27[Atlassian AtlasKit](https://atlaskit.atlassian.com),
28[ReachUI](https://ui.reach.tech/),
29[SmoothUI](https://smooth-ui.smooth-code.com/),
30[Storybook](https://storybook.js.org/)
31and we will do our best to earn your trust too!
32
33# Features
34 - no keyboard control, everything is done watching a __focus behavior__, not emulating it. Thus works always and everywhere.
35 - 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).
36 - _Scattered_ locks, or focus lock groups - you can setup different isolated locks, and _tab_ from one to another.
37 - Controllable isolation level.
38 - variable size bundle. Uses sidecar to trim UI part to 1.5kb.
39
40> 💡 __focus__ locks is only the first part, there are also __scroll lock__ and __text-to-speech__ lock
41you have to use to really "lock" the user.
42Try [react-focus-on](https://github.com/theKashey/react-focus-on) to archive everything above, assembled in the right order.
43
44# How to use
45Just wrap something with focus lock, and focus will be `moved inside` on mount.
46```js
47 import FocusLock from 'react-focus-lock';
48
49 const JailForAFocus = ({onClose}) => (
50 <FocusLock>
51 You can not leave this form
52 <button onClick={onClose} />
53 </FocusLock>
54 );
55```
56Demo - https://codesandbox.io/s/5wmrwlvxv4.
57
58# WHY?
59From [MDN Article about accessible dialogs](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_dialog_role):
60 - The dialog must be properly labeled
61 - Keyboard __focus must be managed__ correctly
62
63This one is about managing the focus.
64
65I've got a good [article about focus management, dialogs and WAI-ARIA](https://medium.com/@antonkorzunov/its-a-focus-trap-699a04d66fb5).
66
67# API
68> FocusLock would work perfectly even with no props set.
69
70 FocusLock has few props to tune behavior, all props are optional:
71 - `[disabled`, to disable(enable) behavior without altering the tree.
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.
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
85### Focusing in OSX (Safari/FireFox) is strange!
86By default `tabbing` in OSX `sees` only controls, but not links or anything else `tabbable`. This is system settings, and Safari/FireFox obey.
87Press Option+Tab in Safary to loop across all tabbables, or change the Safary 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.
88
89## Set up
90### Requirements
91- version 1x is React 15/16 compatible
92- version 2+ requires React 16.8+ (hooks)
93### Import
94`react-focus-lock` exposed __3 entry points__: for the classical usage, and a _sidecar_ one.
95#### Default usage
96- 4kb, `import FocusLock from 'react-focus-lock` would give you component you are looking for.
97
98#### Separated usage
99Meanwhile - you dont need any focus related logic until it's needed.
100Thus - you may defer that logic till Lock activation and move all related code to a _sidecar_.
101
102- UI, __1.5kb__, `import FocusLockUI from 'react-focus-lock/UI` - a DOM part of a lock.
103- Sidecar, 3.5kb, `import Sidecar from 'react-focus-lock/sidecar` - which is the real focus lock.
104
105```js
106import FocusLockUI from "react-focus-lock/UI";
107import {sidecar} from "use-sidecar";
108
109// prefetch sidecar. data would be loaded, but js would not be executed
110const FocusLockSidecar = sidecar(
111 () => import(/* webpackPrefetch: true */ "react-focus-lock/sidecar")
112);
113
114<FocusLockUI
115 disabled={this.state.disabled}
116 sideCar={FocusLockSidecar}
117>
118 {content}
119</FocusLockUI>
120```
121That would split FocusLock into two pieces, reducing app size and improving the first load.
122The cost of focus-lock is just 1.5kb!
123
124> Saved 3.5kb?! 🤷‍♂️ 3.5kb here and 3.5kb here, and your 20mb bundle is ready.
125
126# Autofocus
127 As long you cannot use `autoFocus` prop -
128 cos "focusing" should be delayed to Trap activation, and autoFocus will effect immediately -
129 Focus Lock provide a special API for it
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 gonna to be unmounted - focus will be returned after zero-timeout.
253 - In case `returnFocus` did not set, and you are going to control focus change by your own - keep in mind
254 >> React will first call Parent.componentWillUnmount, and next Child.componentWillUnmount
255
256 Thus means - Trap will be still active, be the time you _may_ want move(return) focus on componentWillUnmount. Please deffer this action with a zero-timeout.
257
258# Not only for React
259 Uses [focus-lock](https://github.com/theKashey/focus-lock/) under the hood. It does also provide support for Vue.js and Vanilla DOM solutions
260
261# Warning!
262Two different _focus-lock-managers_ or even different version of a single one, active
263simultaneously will FIGHT!
264
265__Focus-lock will surrender__, as long any other focus management library will not.
266
267## Focus fighting
268You may wrap some render branch with `FreeFocusInside`, and react-focus-lock __will ignore__
269any focus inside marked node, thus landing a peace.
270
271```js
272import { FreeFocusInside } from 'react-focus-lock';
273
274<FreeFocusInside>
275 <div id="portal-for-modals">
276 in this div i am going to portal my modals, dont fight with them please
277 </div>
278</FreeFocusInside>
279```
280
281Even the better is to `whiteList` FocusLock areas - for example "you should handle only React Stuff in React Root"
282```js
283<FocusLock whiteList={node => document.getElementById('root').contains(node)}>
284 ...
285</FocusLock>
286```
287
288PS: __please use webpack or yarn resolution for force one version of react-focus-lock used__
289
290> webpack.conf
291```js
292 resolve: {
293 alias: {
294 'react-focus-lock': path.resolve(path.join(__dirname, './node_modules/react-focus-lock'))
295 ...
296```
297
298# More
299To create a "right" modal dialog you have to:
300- manage a focus. Use this library
301- block document scroll. Use [react-scroll-locky](https://github.com/theKashey/react-scroll-locky).
302- hide everything else from screen readers. Use [aria-hidden](https://github.com/theKashey/aria-hidden)
303
304You may use [react-focus-on](https://github.com/theKashey/react-focus-on) to archive everything above, assembled in the right order.
305
306# Licence
307 MIT
308
309