Giter VIP home page Giter VIP logo

react-focus-lock's Introduction

REACT FOCUS LOCK

it-is-a-trap

The way to manage your focus.
The way to lock it inside.
The way to team up with a11y.

Make you site a better place. For everyone.

CircleCI status npm


It is a trap! We got your focus and will not let him out!

NPM

This is a small library, but very useful for:

  • Modal dialogs. You can not leave it with "Tab", ie do a "tab-out".
  • Focused tasks. It will aways brings you back, as you can "lock" user inside a component.

You have to lock every modal dialog, that's what a11y is asking for.

And this is most comprehensive focus lock/trap ever built.

Features

  • no keyboard control, everything is done watching a focus behavior, not emulating it. Thus works always and everywhere.
  • React Portals support. Even if some data is in outerspace - it is still in lock.
  • Scattered locks, or focus lock groups - you can setup different isolated locks, and tab from one to another.
  • Controllable isolation level.

How to use

Just wrap something with focus lock, and focus will be moved inside on mount.

 import FocusLock from 'react-focus-lock';

 const JailForAFocus = ({onClose}) => (
    <FocusLock>
      You can not leave this form
      <button onClick={onClose} />
    </FocusLock>
 );

Demo - https://codesandbox.io/s/5wmrwlvxv4.

WHY?

From MDN Article about accessible dialogs:

  • The dialog must be properly labeled
  • Keyboard focus must be managed correctly

This one is about managing the focus.

I've got a good article about focus management, dialogs and WAI-ARIA.

API

FocusLock has few props to tune behavior

  • disabled, to disable(enable) behavior without altering the tree.
  • returnFocus, to return focus into initial position on unmount(not disable).

By default returnFocus is disabled, so FocusLock will not restore original focus on deactivation.

This is expected behavior for Modals, but it is better to implement it by your self.

  • persistentFocus, default false, requires any element to be focused. This also disables text selections inside, and outside focus lock.
  • autoFocus, default true, enables or disables focusing into on Lock activation. If disabled Lock will blur an active focus.
  • noFocusGuards disabled focus guards - virtual inputs which secure tab index.
  • group named focus group for focus scattering aka combined lock targets
  • 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.
  • whiteList you could whitelist locations FocusLock should carry about. Everything outside it will ignore. For example - any modals.
  • as if you need to change internal div element, to any other. Use ref forwarding to give FocusLock the node to work with.
  • lockProps to pass any extra props (except className) to the internal wrapper.

Focusing in OSX (Safary/FireFox) is strange!

By default tabbing in OSX sees only controls, but not links or anything else tabbable. This is system settings, and Safari/FireFox obey. Press 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 for more information.

Autofocus

As long you cannot use autoFocus prop - cos "focusing" should be delayed to Trap activation, and autoFocus will effect immediately - Focus Lock provide a special API for it

  • prop data-autofocus on the element.
  • prop data-autofocus-inside on the element to focus on something inside.
  • AutoFocusInside component, as named export of this library.
 import FocusLock, { AutoFocusInside } from 'react-focus-lock';
 
 <FocusLock>
   <button>Click</button>
   <AutoFocusInside>
    <button>will be focused</button>
   </AutoFocusInside>
 </FocusLock>
 // is the same as
 
 <FocusLock>
   <button>Click</button>
    <button data-autofocus>will be focused</button>
 </FocusLock>
 
 <FocusLock as="section">
    <button>Click</button>
    <button data-autofocus>will be focused</button>
 </FocusLock>
 
 <FocusLock as={AnotherComponent} lockProps={{anyProp: 4}}>
    <button>Click</button>
    <button data-autofocus>will be focused</button>
 </FocusLock>

If there is more than one auto-focusable target - the first will be selected. If it is a part of radio group, and rest of radio group element are also autofocusable(just put them into AutoFocusInside) - checked one fill be selected.

AutoFocusInside will work only on Lock activation, and does nothing, then used outside of the lock. You can use MoveFocusInside to move focus inside with or without lock.

 import { MoveFocusInside } from 'react-focus-lock';
    
 <MoveFocusInside>
  <button>will be focused</button>
 </MoveFocusInside>

Portals

Use focus scattering to handle portals

  • using groups. Just create a few locks (only one could be active) with a same group name
const PortaledElement = () => (
   <FocusLock group="group42" disabled={true}>
     // "discoverable" portaled content
   </FocusLock>  
);

<FocusLock group="group42">
  // main content
</FocusLock>
  • using shards. Just pass all the pieces to the "shards" prop.
const PortaledElement = () => (
   <div ref={ref}>
     // "discoverable" portaled content
   </div>  
);

<FocusLock shards={[ref]}>
  // main content
</FocusLock>
  • without anything. FocusLock will not prevent focusing portaled element, but will not include them in to tab order
const PortaledElement = () => (
   <div>
     // NON-"discoverable" portaled content
   </div>  
);

<FocusLock shards={[ref]}>
  // main content
  <PortaledElement />
</FocusLock>

Guarding

As you may know - FocusLock is adding Focus Guards before and after lock to remove some side effects, like page scrolling. But shards will not have such guards, and it might be not so cool to use them - for example if no tabbable would be defined after shard - you will tab to the browser chrome.

You may wrap shard with InFocusGuard or just drop InFocusGuard here and there - that would solve the problem.

import {InFocusGuard} from 'react-focus-lock';

<InFocusGuard>
  <button>
</InFocusGuard>

//

<InFocusGuard />
<button>
<InFocusGuard />

InFocusGuards would be active(tabbable) only when tabble, it protecting, is focused.

Automatic potral discovery

Unmounting and focus management

  • In case FocusLock has returnFocus enabled, and it's gonna to be unmounted - focus will be returned after zero-timeout.
  • In case returnFocus did not set, and you are going to control focus change by your own - keep in mind

React will first call Parent.componentWillUnmount, and next Child.componentWillUnmount

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.

How it works

Everything thing is simple - react-focus-lock just don't let focus leave boundaries of a component, and is doing something only if escape attempt was successful.

It is not altering tabbing behavior at all. We are good citizens.

Not only for React

Uses focus-lock under the hood. It does also provide support for Vue.js and Vanilla DOM solutions

Warning!

Two different focus-lock-managers or even different version of a single one, active simultaneously will FIGHT!

Focus-lock will surrender, as long any other focus management library will not.

Focus fighting

You may wrap some render branch with FreeFocusInside, and react-focus-lock will ignore any focus inside marked node, thus landing a peace.

import { FreeFocusInside } from 'react-focus-lock';

<FreeFocusInside>
 <div id="portal-for-modals">
   in this div i am going to portal my modals, dont fight with them please
 </div>
</FreeFocusInside>

Even the better is to whiteList FocusLock areas - for example "you should handle only React Stuff in React Root"

<FocusLock whiteList={node => document.getElementById('root').contains(node)}>
 ...
</FocusLock>

PS: please use webpack or yarn resolution for force one version of react-focus-lock used

webpack.conf

 resolve: {    
    alias: {
      'react-focus-lock': path.resolve(path.join(__dirname, './node_modules/react-focus-lock'))
 ...

More

To create a "right" modal dialog you have to:

You may use react-focus-on to archive everything above, assembled in the right order.

Package size

About 3kb, minified and gzipped.

Licence

MIT

react-focus-lock's People

Contributors

ai avatar ezhlobo avatar jefski14 avatar jossmac avatar ndelangen avatar thekashey avatar toolchild avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.