JavaScript
The JS API for Undernet is fairly straightforward. The main rule of thumb is to only use the JS when you know the DOM is ready. You can choose to start all components on the page or simply choose one by its id.
Enabling Undernet, including all its component scripts, is as easy as this:
<!-- At the end of body content -->
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/undernet@8.1.0/dist/undernet.bundle.min.js"
integrity="sha256-am6vAHMcM/VM5t7fA2/WTBPL4UbehfbYc4M7aLFe824="
crossorigin="anonymous"
></script>
<script type="text/javascript">
// Undernet is now attached to the `window`
if (document)
document.addEventListener("DOMContentLoaded", function() {
Undernet.start()
})
</script>
Core API
You can enable and disable all components on the global Undernet
object using the start
and stop
methods.
start
This method starts and sets up events for one or more components on the page.
Undernet.start(id, useFocusRing)
Arguments
id
(string)
Default: undefined
Initializes one of each component with the specified data-*
attribute value (e.g., its component id). If a component isn’t found, nothing happens.
useFocusRing
(boolean)
Default: false
Enables the focus ring utility. Learn more.
stop
This method tears down events for one or more components on the page. If the component has an overlay of any kind, such as a dropdown or modal, the overlay is closed before the teardown.
Undernet.stop(id, disableFocusRing)
Arguments
id
(string)
Default: undefined
Runs a teardown of one of each component with the specified data-*
attribute value (e.g., its component id). If a component isn’t found, nothing happens.
disableFocusRing
(boolean)
Default: false
Disables the focus ring utility and removes the using-keyboard
class from the page.
Using Modules
If you use npm, the default export is the Undernet
object, whose API is the same as above.
import Undernet from "undernet"
Undernet.start()
You can also do a named import of just one component or utility. Bonus: it’s tree-shakable if you use tools which enable the feature (webpack and rollup, for example).
import { Modals } from "undernet"
Modals.start()
Handling Single Components
By default, using start
or stop
will cause Undernet to search the entire document
for components. When using a UI framework such as React, however, that isn’t desirable. Instead, you can start/stop a single component instance by passing the unique component id to the API call (e.g., data-collapsible='some-unique-id'
would be initialized by start('some-unique-id)
).
Here’s an example of how that looks in practice with React hooks:
import { Collapsibles } from "undernet"
const COLLAPSIBLE_ID = "sidebar-collapsible"
export default function Sidebar(props) {
useEffect(() => {
Collapsibles.start(COLLAPSIBLE_ID)
return () => Collapsibles.stop(COLLAPSIBLE_ID)
}, [])
return (
<div data-collapsible={COLLAPSIBLE_ID} class="collapsible">
...
</div>
)
}
When the component mounts, useEffect
triggers start
on the collapsible returned in JSX. Then, the stop
call is made in the effect’s return value, or when the component is about to be unmounted.
Handling DOM State
If you’re removing/adding nodes from/to the DOM, you’ll need to be careful. Undernet isn’t smart enough to know that your DOM changed. Luckily most UI frameworks provide render-tied lifecycle hooks, so we can piggy back off of those!
Let’s extend the sidebar example from before, but this time we’ll toggle its visibility using a button:
const COLLAPSIBLE_ID = "sidebar-collapsible"
export default function Sidebar(props) {
// 1
const [sidebarIsVisible, setSidebarIsVisible] = useState(true)
// 2
useEffect(() => {
return () => Collapsibles.stop(COLLAPSIBLE_ID)
}, [])
// 4
useEffect(() => {
if (sidebarIsVisible) Collapsibles.start(COLLAPSIBLE_ID)
}, [sidebarIsVisible])
// 3
const handleClick = e => {
if (sidebarIsVisible) Collapsibles.stop(COLLAPSIBLE_ID)
setSidebarIsVisible(!sidebarIsVisible)
}
return (
<>
<button onClick={handleClick}>Toggle Sidebar</button>
{sidebarIsVisible && (
<div data-collapsible={COLLAPSIBLE_ID} class="collapsible">
...
</div>
)}
</>
)
}
Using the comments above, here are the steps that occur:
-
We create a state variable and state setter to indicate when the sidebar collapsible is visible. The sidebar is visible by default.
-
When the component mounts, we simply return the
stop
call, as we’ll be handlingstart
elsewhere. In this case,stop
will be called when the component is unmounted. Callingstop
even if the component isn’t visible is perfectly safe. -
Every time the toggle button is clicked, we check if the sidebar is visible. If it is, we call
stop
on the collapsible to clean up events in preparation for removing the component HTML from the page. Then, we setsidebarIsVisible
to its opposite boolean value. -
Finally, a
useEffect
block runs becausesidebarIsVisible
was updated. If the new value istrue
, or the sidebar was newly made visible on the page, we runstart
to make the collapsible work again. Otherwise nothing happens.
Utilities
Undernet comes with two utilities out of the box: createFocusRing
and createFocusTrap
. They can be initialized with start
and stop
methods as well. The signature of these utilities are different, however.
createFocusRing
Create global event listeners on the page for keyboard and mouse behavior.
import { createFocusRing } from "undernet"
const focusRing = createFocusRing()
focusRing.start()
If tab
, space
, enter
, or arrow keys are being used, you’re in “keyboard mode,” enabling a bright focus ring around the actively focused element.
As soon as a mouse is in use again, the ring goes away.
Methods
start
Begin listening for keyboard/mouse.
stop
Stop listening for keyboard/mouse.
createFocusTrap
Create a focus trap within an container, optionally with custom behavior.
This utility is offered in case you need the functionality outside of the components provided in Undernet.
The function takes two arguments.
import { createFocusTrap } from "undernet"
const focusTrap = createFocusTrap(selector, options)
focusTrap.start()
Arguments
selector
(string)
Required
A string to be queried in the DOM; it will be treated as the container of possible focusable elements. If this is the only parameter given, tab
and shift+tab
will be the key-bindings used for trapping.
const focusTrap = createFocusTrap(".some-wrapper-element")
options
(object)
Default: {}
Customize trapping behavior using the below options.
options.useArrows
(boolean)
Default: false
Trap focus using up and down arrows.
const focusTrap = createFocusTrap(".some-wrapper-element", { useArrows: true })
options.children
(array)
Default: []
Provide a custom array of elements to trap focus within. This overrides the element querying functionality of the utility.
const children = document.querySelectorAll(".focusable-element-class")
const focusTrap = createFocusTrap(".wrapper-element", { children })
NOTE: You should still pass a selector string for the wrapper as a fallback, in case children
comes back empty. The main benefit being the selector will bail gracefully if there are still no children to trap focus on.
options.matchers
(array)
Default: ["a", "button", "input", "object", "select", "textarea", "[tabindex]"]
Override the default matchers for focusable elements. Elements with is-visually-hidden
are always excluded from the resulting focusable elements.
const focusTrap = createFocusTrap(".wrapper-element", {
matchers: ["button", "input", ".elements-with-this-class"],
})
Methods
start
Begin trapping.
stop
Stop trapping.
Is there information missing? Edit this page on Github!