Potato logoACE
Skip to main contentGitLab logo

Listbox

Listbox is a list of options that allows users to select one (single-select) or more (multi-select) using a mouse or keyboard. It mimics a native HTML <select> that has attribute size with a value or 1 or higher, while allows more styling flexibility.

Listbox conforms to the W3C WAI-ARIA authoring practices.

Set up

First import the styles into your main SASS file, replacing <path-to-node_modules> with the path to the node_modules directory relative to the file:

@import '<path-to-node_modules>/@potato/ace/components/listbox/listbox';

Alternatively ace.scss includes all ACE component SASS files, so if using multiple ACE components it can be imported instead:

@import '<path-to-node_modules>/@potato/ace/ace';

A CSS file is also provided for convenience and is located at <path-to-node_modules>/@potato/ace/components/listbox/ace-listbox.css.

Then import the class into your JavaScript entry point:

import '<path-to-node_modules>/@potato/ace/components/listbox/listbox';

For convenience the ES6 class is exported as Listbox and the attribute names used by the class are exported as properties of ATTRS.

After the event DOMContentLoaded is fired on document an instance of Listbox is instantiated within each <ace-listbox> element and an ID ace-listbox-<n> is added for any instance without one, where <n> is a unique integer. Once instantiation is complete a custom event ace-listbox-ready is dispatched to window. See the Custom events section below for more details.

Listbox must have a descendant list and will use a <ul> or <ol> with attribute ace-listbox-list. If no descendant has this attribute then the first descendant <ul> or <ol> will be used and given this attribute. It is strongly recommended that the list element be provided with an accessible label using aria-label or aria-labelledby. The list can be empty upon instantiation and options can be dynamically added or removed as long as custom event ace-listbox-update-options is dispatched to the Listbox instance afterwards.

There are two main types of Listboxes, single-select and multi-select. Single-select Listboxes allow selection of only a single option at a time and are instantiated by default. Multi-select Listboxes allow selection of multiple options and are instantiated in Listboxes with attribute ace-listbox-multiselect.

Listbox options can be active and/or selected. Only a single option can be active at once and only when the Listbox list has focus. The active option is given the ace-listbox-active-option attribute and its ID is stored as the value of Listbox list's aria-activedescendant attribute. Both attributes are removed when the Listbox list loses focus as no option is active then.

If using a Listbox in a HTML <form> the attribute ace-listbox-for-form can be added to it which causes it to create a hidden <input> with attribute ace-listbox-input. The values of any selected options are stored as the value of the <input> in the form of a URI encoded, JSON-strigified array. Similarly, the IDs of any selected options are stored as the value of the <input> attribute data-ace-listbox-selected-option-ids in the form of a JSON strigified array.

Usage

The active option can be changed by clicking on an option, using , , Home or End, or by typing one or more characters making the next option with text starting with the typed string active. will loop around from the top of the list to the bottom, while will loop from the bottom to the top. Similarly, when a character is typed if the bottom of the list is reached the search will loop around and continute from the top until a match or the starting option is reached. Repeatedly pressing the same character with a delay in-between will loop through all matching options. The value of this delay is 500ms and is exported by Listbox as SEARCH_TIMEOUT.

The active option is always the selecteed option in a single-select Listbox. In a multi-select Listbox an option's selected state can be toggled by clicking on it or pressing Space if it's active. Selection of multiple options can be achieved by clicking on an option and then clicking on another one while holding , which will select all options in between the two clicked ones. Multiple options can also be selected using the keyboard in the following ways:

Styles

The following SASS is applied to Listbox. The SASS variables use !default so can also be easily overridden by users. SASS variables used that are not defined here are defined in <path-to-node_modules>/@potato/ace/common/constants.scss.

To conform to W3 WAI-ARIA practices, active and selected options must be visually distinct from other options and one another. For this reason the active and selected option were given an outline and a background color respectively.

@import '../../common/constants';


// VARIABLES
$ace-listbox-active-option-outline-color: slategrey !default;
$ace-listbox-active-option-outline-style: dotted !default;
$ace-listbox-active-option-outline-width: 2px !default;
$ace-listbox-list-height: auto !default;
$ace-listbox-selected-option-bg-color: $ace-color-selected !default;
$ace-listbox-selected-option-text-color: #fff !default;


// STYLES
[ace-listbox-list] {
	height: $ace-listbox-list-height;
	list-style-position: inside;
	overflow-y: auto;
	user-select: none;

	[aria-selected="true"] {
		background: $ace-listbox-selected-option-bg-color;
		color: $ace-listbox-selected-option-text-color;
	}
}

[ace-listbox-multiselect] [ace-listbox-option-active] {
	outline: $ace-listbox-active-option-outline-width $ace-listbox-active-option-outline-style $ace-listbox-active-option-outline-color;
}

Custom events

Listbox uses the following custom events, the names of which are available in its exported EVENTS object, similar to ATTRS, so they may be imported into other modules.

Dispatched event

The following event is dispatched to window by Listbox.

Ready

ace-listbox-ready

This event is dispatched when Listbox finishes initialising just after page load, and after dynamically added options are initialised in response to the ace-listbox-update-options custom event being dispatched. The event name is available as EVENTS.OUT.READY and its detail property is composed as follows:

'detail': {
	'id': // ID of Listbox [string]
}

Listened for event

Listbox listens for the following event that should be dispatched to window.

Update options

ace-listbox-update-options

This event should be dispatched when options are added to or removed from the list and causes Listbox to initialise them and then dispatch the ace-listbox-ready event. The event name is available as EVENTS.IN.UPDATE_OPTIONS and its detail property should be composed as follows:

'detail': {
	'id': // ID of target Listbox [string]
}

Examples

Each example contains a live demo and the HTML code that produced it. The code shown may differ slightly to that rendered for the demo as some components may alter their HTML when they initialise.

Single-select Listbox

A single-select Listbox with a descendant <ul> list and <label>.

  • Iron Man
  • Nick Fury
  • Hulk
  • Thor
  • Captain America
  • Black Widow
  • Scarlet Witch
  • Ant-Man
  • Black Panther
  • Spider-man
  • Doctor Strange
  • Captain Marvel
<ace-listbox>
	<label id="single-select-listbox-label">Choose an Avenger:</label>
	<ul aria-labelledby="single-select-listbox-label">
		<li>Iron Man</li>
		<li>Nick Fury</li>
		<li>Hulk</li>
		<li>Thor</li>
		<li>Captain America</li>
		<li>Black Widow</li>
		<li>Scarlet Witch</li>
		<li>Ant-Man</li>
		<li>Black Panther</li>
		<li>Spider-man</li>
		<li>Doctor Strange</li>
		<li>Captain Marvel</li>
	</ul>
</ace-listbox>

Multi-select Listbox

A multi-select Listbox.

  • Iron Man
  • Nick Fury
  • Hulk
  • Thor
  • Captain America
  • Black Widow
  • Scarlet Witch
  • Ant-Man
  • Black Panther
  • Spider-man
  • Doctor Strange
  • Captain Marvel
<ace-listbox ace-listbox-multiselect id="ace-multiselect-listbox">
	<label id="multiselect-listbox-label">Choose an Avenger:</label>
	<ul aria-labelledby="multiselect-listbox-label">
		<li>Iron Man</li>
		<li>Nick Fury</li>
		<li>Hulk</li>
		<li>Thor</li>
		<li>Captain America</li>
		<li>Black Widow</li>
		<li>Scarlet Witch</li>
		<li>Ant-Man</li>
		<li>Black Panther</li>
		<li>Spider-man</li>
		<li>Doctor Strange</li>
		<li>Captain Marvel</li>
	</ul>
</ace-listbox>

Listbox for forms

A multi-select Listbox to be used with HTML forms with a descendant <ol> list, an external <label> and a hidden <input> with the selected option data.

  1. Iron Man
  2. Nick Fury
  3. Hulk
  4. Thor
  5. Captain America
  6. Black Widow
  7. Scarlet Witch
  8. Ant-Man
  9. Black Panther
  10. Spider-man
  11. Doctor Strange
  12. Captain Marvel
<label id="ol-listbox-label">Choose an Avenger:</label>
<ace-listbox ace-listbox-multiselect ace-listbox-for-form id="ace-listbox-for-form">
	<ol aria-labelledby="ol-listbox-label">
		<li>Iron Man</li>
		<li>Nick Fury</li>
		<li>Hulk</li>
		<li>Thor</li>
		<li>Captain America</li>
		<li>Black Widow</li>
		<li>Scarlet Witch</li>
		<li>Ant-Man</li>
		<li>Black Panther</li>
		<li>Spider-man</li>
		<li>Doctor Strange</li>
		<li>Captain Marvel</li>
	</ol>
</ace-listbox>

Listbox with dynamic options

In this example the Listbox instantiates with an empty <ul> that can be populated with options using Add option. The first option can also be removed using Remove option. Both these buttons dispatch the ace-listbox-update-options event that updates the Listbox. The extra JavaScript code required by this example is also included below.


    <button id="add-option">
    	Add option
    </button>
    <button id="remove-option">
    	Remove option
    </button>
    <hr>
    <ace-listbox id="ace-custom-events-listbox">
    	<label id="custom-events-listbox-label">Choose an Avenger:</label>
    	<ul aria-labelledby="custom-events-listbox-label"></ul>
    </ace-listbox>
    
    import { EVENTS } from '/ace/components/listbox/listbox.js';
    
    document.addEventListener('DOMContentLoaded', () => {
    	const LISTBOX_ID = 'ace-custom-events-listbox';
    	const listboxEl = document.getElementById(LISTBOX_ID);
    
    	const updateOptions = () => window.dispatchEvent(new CustomEvent(
    		EVENTS.IN.UPDATE_OPTIONS,
    		{'detail': {'id': LISTBOX_ID}},
    	));
    
    	document.getElementById('add-option')
    		.addEventListener('click', () => {
    			listboxEl.querySelector('ul').innerHTML += '<li>New Option</li>';
    			updateOptions();
    		});
    
    	document.getElementById('remove-option')
    		.addEventListener('click', () => {
    			const listboxListEl = listboxEl.querySelector('ul');
    			const fistOptionEl = listboxListEl.querySelector('li');
    			if (!fistOptionEl) {
    				return;
    			}
    			listboxListEl.removeChild(fistOptionEl);
    			updateOptions();
    		});
    });
    

    Styled Listbox

    An example of how Listbox can be styled, with the applied CSS shown below.

    • Iron Man
    • Nick Fury
    • Hulk
    • Thor
    • Captain America
    • Black Widow
    • Scarlet Witch
    <ace-listbox class="styled-listbox">
    	<label id="styled-listbox-label" class="styled-listbox__label">Choose an Avenger:</label>
    	<ul aria-labelledby="styled-listbox-label" class="styled-listbox__list">
    		<li class="styled-listbox__option">
    			<img class="styled-listbox__img" role="presentation" src="/img/logo.svg">
    			Iron Man
    		</li>
    		<li class="styled-listbox__option">
    			<img class="styled-listbox__img" role="presentation" src="/img/logo.svg">
    			Nick Fury
    		</li>
    		<li class="styled-listbox__option">
    			<img class="styled-listbox__img" role="presentation" src="/img/logo.svg">
    			Hulk
    		</li>
    		<li class="styled-listbox__option">
    			<img class="styled-listbox__img" role="presentation" src="/img/logo.svg">
    			Thor
    		</li>
    		<li class="styled-listbox__option">
    			<img class="styled-listbox__img" role="presentation" src="/img/logo.svg">
    			Captain America
    		</li>
    		<li class="styled-listbox__option">
    			<img class="styled-listbox__img" role="presentation" src="/img/logo.svg">
    			Black Widow
    		</li>
    		<li class="styled-listbox__option">
    			<img class="styled-listbox__img" role="presentation" src="/img/logo.svg">
    			Scarlet Witch
    		</li>
    	</ul>
    </ace-listbox>
    
    .styled-listbox {
    	&__label,
    	&__input,
    	&__option {
    		font-family: 'Roboto', sans-serif;
    		font-size: 14px;
    	}
    
    	&__list {
    		border: 1px solid #837b8b;
    		border-radius: 4px;
    		height: 225px;
    		margin-top: 10px;
    		max-width: 300px;
    		width: 100%;
    
    		&:focus {
    			outline-color: #41354d;
    		}
    	}
    
    	&__option {
    		align-items: center;
    		display: flex;
    		padding: 10px 16px;
    
    		&[aria-selected="true"] {
    			background: #41354d;
    		}
    	}
    
    	&__img {
    		height: 2em;
    		margin-right: 10px;
    	}
    }