ctrly
Lightweight and dependency-free content toggling with a focus on accessibility.
About
ctrly is a lightweight library which tries to solve 80% of the use-cases where
a control (usually a <button>
element) toggles the visibility of a
target element.
It can be used to implement dropdown or off-canvas menus, modals, accordions
and similar UI elements. Example implementations can be found in the
examples/
directory.
Minified and gzipped, the total footprint weights about 2.7kB.
Installation
The recommended way to install ctrly is via npm.
npm install ctrly
After installation, ctrly
can be imported as a module.
import ctrly from 'ctrly';
The latest version can also be downloaded or included from
unpkg or
jsDelivr.
<script src="/path/to/ctrly.js"></script>
The ctrly()
function is then globally available.
Usage
A typical setup includes a control (usually a <button>
element) which
toggles the visibility of a target element.
The control must have a data-ctrly
which must contain the ID of the target.
<button data-ctrly="my-target">Toggle</button>
<section id="my-target">You clicked the toggle to make me visible</section>
The specification also
allows a
ID reference list
(a list of multiple, space-separated ID references) as value of the
arial-controls
attribute.
This is not supported by ctrly and only a single ID reference is
allowed.
To initialize all controls, the ctrly()
function must be called once.
ctrly();
ctrly then adds all required ARIA attributes, the aria-controls
and
aria-expanded
attributes to the control and the aria-hidden
and
aria-labelledby
to the target.
If the control does not have an id
attribute, ctrly will add an auto-generated
ID.
The fully generated HTML looks like the following.
<button
data-ctrly="my-target"
id="ctrly-control-1"
aria-controls="my-target"
aria-expanded="false">
Toggle
</button>
<section
id="my-target"
aria-hidden="false"
aria-labelledby="ctrly-control-1"
>
You clicked the toggle to make me visible
</section>
Note: ctrly does not ship with any default CSS which shows and hides the
target element as it makes no assumptions on how the visibility is controlled.
This must be implemented depending on the aria-hidden
attribute which is
toggled to either false
or true
by ctrly.
.target-selector[aria-hidden="true"] {
display: none;
}
.target-selector[aria-hidden="true"] {
visibility: hidden;
}
It is also good practice to hide the controls if JavaScript is disabled.
This can be done depending on the presence of the aria-controls
attribute
added by ctrly.
.control-selector:not([aria-controls]) {
display: none;
}
API
The return value of the ctrly()
function is an object with the following
functions.
destroy
This function closes all open targets and unbinds all event listeners.
Example
const { destroy } = ctrly();
destroy();
init
This function (re)initializes all controls. This can be useful after the DOM
has been updated, eg. controls have been added dynamically.
Example
const { init } = ctrly();
init();
Options
ctrly's behavior can be controlled by passing an options object as the first
argument.
ctrly({
});
The following options are available.
selector
Default: [data-ctrly]
A selector for the control elements.
Example
<button class="my-control" data-ctrly="my-target">Toggle</button>
ctrly({
selector: '.my-control'
});
context
Default: null
A selector to group targets together. Can be used in combination with the
allowMultiple option to allow or disallow multiple open
targets inside a context.
See the accordion example for a use-case.
Example
<div class="my-context">
<button data-ctrly="my-target">Toggle</button>
</div>
<div class="my-context">
<button data-ctrly="my-target">Toggle</button>
</div>
ctrly({
context: '.my-context'
});
focusTarget
Default: true
By default, once the target becomes visible, the focus is shifted to the first
focusable element inside the target. Passing false
as an option disables this
behavior.
Example
ctrly({
closeOnScroll: false
});
closeOnBlur
Default: true
By default, targets are closed when the focus is shifted from an element inside
the target to an element outside the target. Passing false
as an option
disables this behavior.
This setting is always false
if trapFocus
is set
to true
.
Example
ctrly({
closeOnBlur: false
});
closeOnEsc
Default: true
By default, targets are closed when the ESC key is pressed. Passing
false
as an option disables this behavior.
Example
ctrly({
closeOnEsc: false
});
closeOnOutsideClick
Default: true
By default, targets are closed when there is a mouse click outside the target.
Passing false
as an option disables this behavior.
Example
ctrly({
closeOnOutsideClick: false
});
closeOnScroll
Default: false
Passing true
as an option closes a target when the window is scrolled and
the mouse is currently not inside the target element.
Example
ctrly({
closeOnScroll: true
});
trapFocus
Default: false
Passing true
as an option ensures that TAB and
SHIFT+TAB do not move focus outside the target.
Example
ctrly({
closeOnScroll: false
});
allowMultiple
Default: false
By default, if a target becomes visible, all other open targets are closed.
Passing true
as an option allows multiple targets to be opened at the same
time.
This can be combined with the context option to only allow multiple
open targets inside a context element.
See the accordion example for a use-case.
To allow multiple open targets, closeOnBlur
must be set to
false
.
Example
ctrly({
allowMultiple: true
});
on
Default: {}
Allows to define event callbacks as {event: callback}
pairs.
Example
ctrly({
on: {
open: target => {
},
opened: target => {
},
close: target => {
},
closed: target => {
}
}
});
autoInit
Default: true
By default, initialization is done when calling ctrly()
. Passing false
as
an option disables this behavior and the init()
method must be called
manually.
Example
const { init } = ctrly({
autoInit: false
});
init();
More information about the event callbacks can be found in the
Events section.
Events
ctrly triggers several events when a target is opened or closed.
There are 2 ways to bind listeners to the events.
- Through the
on
option. - Through DOM event listeners on the target element (the DOM event names are
prefixed with
ctrly:
eg. ctrly:open
).
The following events are available.
open
Triggered before the target element is opened.
Example
ctrly({
on: {
open: target => {
target.classList.add('is-opening');
}
}
});
document.getElementById('my-target').addEventListener('ctrly:open', e => {
const target = e.target;
target.classList.add('is-opening');
});
opened
Triggered after the target element has been opened.
Example
ctrly({
on: {
opened: target => {
target.classList.remove('is-opening');
}
}
});
document.getElementById('my-target').addEventListener('ctrly:opened', e => {
const target = e.target;
target.classList.remove('is-opening');
});
close
Triggered before the target element is closed.
Example
ctrly({
on: {
close: target => {
target.classList.add('is-closing');
}
}
});
document.getElementById('my-target').addEventListener('ctrly:close', e => {
const target = e.target;
target.classList.add('is-closing');
});
closed
Triggered after the target element has been opened.
Example
ctrly({
on: {
closed: target => {
target.classList.remove('is-closing');
}
}
});
document.getElementById('my-target').addEventListener('ctrly:closed', e => {
const target = e.target;
target.classList.remove('is-closing');
});
Thank You
- BrowserStack for providing free VMs for automated testing.
- GitHub for providing free Git repository hosting.
- npm for providing the package manager for JavaScript.
- TravisCI for providing a free build server.
License
Copyright (c) 2018 Jan Sorgalla.
Released under the MIT license.