Gathering detailed insights and metrics for react-focus-lock
Gathering detailed insights and metrics for react-focus-lock
Gathering detailed insights and metrics for react-focus-lock
Gathering detailed insights and metrics for react-focus-lock
npm install react-focus-lock
Updating focus management
Published on 25 Aug 2024
ESM compatible (no, that did not happen)
Published on 20 Apr 2024
React 18 and ShadowDom
Published on 01 May 2022
Shadow of TabIndex
Published on 14 Feb 2022
Return focus 18
Published on 09 Nov 2021
v2.5.2
Published on 04 Jul 2021
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
1,294 Stars
440 Commits
67 Forks
6 Watching
44 Branches
27 Contributors
Updated on 25 Nov 2024
Minified
Minified + Gzipped
JavaScript (100%)
Cumulative downloads
Total Downloads
Last day
-1.7%
375,563
Compared to previous day
Last week
5.6%
2,009,120
Compared to previous week
Last month
9.6%
8,405,782
Compared to previous month
Last year
0.9%
98,265,722
Compared to previous year
2
42
It is a trap! We got your focus and will not let him out!
a11y
is asking for.
Trusted by Atlassian AtlasKit, ReachUI, SmoothUI, Storybook and we will do our best to earn your trust too!
💡 focus locks is part of a bigger whole, consider scroll lock and text-to-speech lock you have to use to really "lock" the user. Try react-focus-on to archive everything above, assembled in the right order.
Just wrap something with focus lock, and focus will be moved inside
on mount.
1 import FocusLock from 'react-focus-lock'; 2 3 const JailForAFocus = ({onClose}) => ( 4 <FocusLock> 5 You can not leave this form 6 <button onClick={onClose} /> 7 </FocusLock> 8 );
Demo - https://codesandbox.io/s/5wmrwlvxv4.
FocusLock would work perfectly even with no props set.
FocusLock has few props to tune behavior, all props are optional:
disabled
, to disable(enable) behavior without altering the tree.className
, to set the className
of the internal wrapper.returnFocus
, to return focus into initial position on unmountBy default
returnFocus
is disabled, so FocusLock will not restore original focus on deactivation. This was done mostly to avoid breaking changes. We strong recommend enabling it, to provide a better user experience.
This is expected behavior for Modals, but it is better to implement it by your self. See unmounting and focus management for details
persistentFocus=false
, requires any element to be focused. This also disables text selections inside, and outside focus lock.autoFocus=true
, enables or disables focusing into on Lock activation. If disabled Lock will blur an active focus.noFocusGuards=false
disabled focus guards - virtual inputs which secure tab index.group='''
named focus group for focus scattering aka combined lock targetsshards=[]
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=fn
you could whitelist locations FocusLock should carry about. Everything outside it will ignore. For example - any modals.as='div'
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.hasPositiveIndices=false
to support a focus lock behavior when any elements tabIndex greater than 0.crossFrame=true
enables aggressive focus capturing within iframesFocus lock exposes a few methods to control focus programmatically.
useFocusInside(nodeRef)
- to move focus inside the given nodeuseFocusScope():{autofocus, focusNext, focusPrev}
- provides API to manage focus within the current lockuseFocusState()
- manages focus state of a given nodeuseFocusController(nodeRef)
- low level version of useFocusScope
working without FocusLock<AutoFocusInside/>
- causes autofocus to look inside the component<MoveFocusInside/>
- wrapper around useFocusInside
, forcibly moves focus inside on mount<FreeFocusInside/>
- hides internals from FocusLock allowing unmanaged focusFocus-lock behavior can be controlled via data-attributes. Declarative API above is working by setting them for you. See corresponding section in focus-lock for details
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 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 for more information.
react-focus-lock
exposed 3 entry points: for the classical usage, and a sidecar one.
import FocusLock from 'react-focus-lock
would give you component you are looking for.Meanwhile - you dont need any focus related logic until it's needed. Thus - you may defer that logic till Lock activation and move all related code to a sidecar.
import FocusLockUI from 'react-focus-lock/UI
- a DOM part of a lock.import Sidecar from 'react-focus-lock/sidecar
- which is the real focus lock.1import FocusLockUI from "react-focus-lock/UI"; 2import {sidecar} from "use-sidecar"; 3 4// prefetch sidecar. data would be loaded, but js would not be executed 5const FocusLockSidecar = sidecar( 6 () => import(/* webpackPrefetch: true */ "react-focus-lock/sidecar") 7); 8 9<FocusLockUI 10 disabled={this.state.disabled} 11 sideCar={FocusLockSidecar} 12> 13 {content} 14</FocusLockUI>
That would split FocusLock into two pieces, reducing app size and improving the first load. The cost of focus-lock is just 1.5kb!
Saved 3.5kb?! 🤷♂️ 3.5kb here and 3.5kb here, and your 20mb bundle is ready.
Use when you cannot use the native autoFocus
prop - because you only want to autofocus once the Trap has been activated
data-autofocus
on the element.data-autofocus-inside
on the element to focus on something inside.AutoFocusInside
component, as named export of this library.1 import FocusLock, { AutoFocusInside } from 'react-focus-lock'; 2 3 <FocusLock> 4 <button>Click</button> 5 <AutoFocusInside> 6 <button>will be focused</button> 7 </AutoFocusInside> 8 </FocusLock> 9 // is the same as 10 11 <FocusLock> 12 <button>Click</button> 13 <button data-autofocus>will be focused</button> 14 </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.
1 import { MoveFocusInside } from 'react-focus-lock'; 2 3 <MoveFocusInside> 4 <button>will be focused</button> 5 </MoveFocusInside>
Use focus scattering to handle portals
groups
. Just create a few locks (only one could be active) with a same group name1const PortaledElement = () => ( 2 <FocusLock group="group42" disabled={true}> 3 // "discoverable" portaled content 4 </FocusLock> 5); 6 7<FocusLock group="group42"> 8 // main content 9</FocusLock>
shards
. Just pass all the pieces to the "shards" prop.1const PortaledElement = () => ( 2 <div ref={ref}> 3 // "discoverable" portaled content 4 </div> 5); 6 7<FocusLock shards={[ref]}> 8 // main content 9</FocusLock>
1const PortaledElement = () => ( 2 <div> 3 // NON-"discoverable" portaled content 4 </div> 5); 6 7<FocusLock shards={[ref]}> 8 // main content 9 <PortaledElement /> 10</FocusLock>
Components
You may use as
prop to change what Focus-Lock will render around children
.
1<FocusLock as="section"> 2 <button>Click</button> 3 <button data-autofocus>will be focused</button> 4 </FocusLock> 5 6 <FocusLock as={AnotherComponent} lockProps={{anyAnotherComponentProp: 4}}> 7 <button>Click</button> 8 <span>Hello there!</span> 9 </FocusLock>
Let's take a look at the Rowing Focus
as an example.
1// Will set tabindex to -1 when is not focused 2const FocusTrackingButton = ({ children }) => { 3 const { active, onFocus, ref } = useFocusState(); 4 return ( 5 <button tabIndex={active ? undefined : -1} onFocus={onFocus} ref={ref}> 6 {children} 7 </button> 8 ); 9}; 10const RowingFocusInternalTrap = () => { 11 const { autoFocus, focusNext, focusPrev } = useFocusScope(); 12 // use useFocusController(divRef) if there is no FocusLock around 13 14 useEffect(() => { 15 autoFocus(); 16 }, []); 17 18 const onKey = (event) => { 19 if (event.key === 'ArrowDown') { 20 focusNext({ onlyTabbable: false }); 21 } 22 if (event.key === 'ArrowUp') { 23 focusPrev({ onlyTabbable: false }); 24 } 25 }; 26 27 return ( 28 <div 29 onKeyDown={onKey} 30 // ref={divRef} for useFocusController 31 > 32 <FocusButton>Button1</FocusButton> 33 <FocusButton>Button2</FocusButton> 34 <FocusButton>Button3</FocusButton> 35 <FocusButton>Button4</FocusButton> 36 </div> 37 ); 38}; 39 40// FocusLock, even disabled one 41const RowingFocusTrap = () => ( 42 <FocusLock disabled> 43 <RowingFocusInternalTrap /> 44 </FocusLock> 45);
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.
1import {InFocusGuard} from 'react-focus-lock'; 2 3// wrap with 4<InFocusGuard> 5 <button /> 6</InFocusGuard> 7 8// place before and after 9<InFocusGuard /> 10<button /> 11<InFocusGuard />
InFocusGuards would be active(tabbable) only when tabble, it protecting, is focused.
If only your modal is the last tabble element on the body - you might remove the Tailing Guard, to allow user tab into address bar.
1<InFocusGuard/> 2<button /> 3// there is no "tailing" guard :)
returnFocus
enabled, and it's going to be unmounted - focus will be returned after zero-timeout.returnFocus
is set to false
, and you are going to control focus change on your own - keep in mindReact will first call Parent.componentWillUnmount, and next Child.componentWillUnmount
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.
Similarly, if you are using the disabled
prop to control FocusLock, you will need a zero-timeout to correctly restore focus.
<FocusLock
disabled={isFocusLockDisabled}
onDeactivation={() => {
// Without the zero-timeout, focus will likely remain on the button/control
// you used to set isFocusLockDisabled = true
window.setTimeout(() => myRef.current.focus(), 0);
}
>
In some cases the original node that was focused before the lock was activated is not the desired node to return focus to. Some times this node might not exists at all.
returnFocus
, letting you decide where to return focus to.1<FocusLock 2 returnFocus={(suggestedNode) => { 3 // somehow activeElement should not be changed 4 if(document.activeElement.hasAttributes('main-content')) { 5 // opt out from default behavior 6 return false; 7 } 8 if (someCondition(suggestedNode)) { 9 // proceed with the suggested node 10 return true; 11 } 12 // handle return focus manually 13 document.getElementById('the-button').focus(); 14 // opt out from default behavior 15 return false; 16 }} 17/>
read more at the issue #83 or mdn article.
To return focus, but without jumpy page scroll returning a focus you might specify a focus option
1<FocusLock 2 returnFocus={{ preventScroll: false }} // working not in all browsers 3>
Not supported by Edge and Safari.
Two different focus-lock-managers or even different version of a single one, being active simultaneously will FIGHT for the focus. This usually totally breaks user experience.
React-Focus-Lock will automatically surrender, letting another library to take the lead.
You may wrap some render branch with FreeFocusInside
, and react-focus-lock will ignore
any focus inside marked node. So in case focus moves to uncontrolled location focus-lock will not trigger letting another library to act without interference in that another location.
1import { FreeFocusInside } from 'react-focus-lock'; 2 3<FreeFocusInside> 4 <div id="portal-for-modals"> 5 in this div i am going to portal my modals, dont fight with them please 6 </div> 7</FreeFocusInside>
Another option for hybrid applications is to whiteList
area where Focus-Lock should act, automatically allowing other managers in other areas.
The code below will scope Focus-Lock on inside the (react)root
element, so anything jQuery can add to the body will be ignored.
1<FocusLock whiteList={node => document.getElementById('root').contains(node)}> 2 ... 3</FocusLock>
React-Focus-Lock is expected to be a singlentone. __Use webpack or yarn resolution for force only one version of react-focus-lock used.
webpack.conf
1 resolve: { 2 alias: { 3 'react-focus-lock': path.resolve(path.join(__dirname, './node_modules/react-focus-lock')) 4 ...
From MDN Article about accessible dialogs:
This one is about managing the focus.
I've got a good article about focus management, dialogs and WAI-ARIA.
Uses focus-lock under the hood. It does also provide support for Vue.js and Vanilla DOM solutions
To create a "right" modal dialog you have to:
You may use react-focus-on to achieve everything above, assembled in the right order.
MIT
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
6 commit(s) and 4 issue activity found in the last 90 days -- score normalized to 8
Reason
Found 3/17 approved changesets -- score normalized to 1
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
118 existing vulnerabilities detected
Details
Score
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More