For Developers
Collection API
Examples
Basic Example
The ListBox
on its own isn't very useful. It registers each item with the model. The
ListBox.Item
only uses the useListItemRegister
hook which handles registration of static items
to the model. The ListBox
uses useListRenderItems
which handles rendering static items as well
as Dynamic List example.
- First
- Second
Identifying Items
A list item takes an optional data-id
property that will be used to identify an item. Without a
data-id
, the identifier will be the item's index when first registered. The basic example has a
data-id
attribute that is a string representation of the index. Providing a data-id
will
override to a value of your choosing. This identifier will be used by other hooks to identify the
item for selection, maintaining a cursor, or anything else.
- First
- Second
Dynamic Items
The ListBox
also handles a dynamic collection of items. Instead of providing each ListBox.Item
statically, provide a render function instead. The function is called with an items
value that is
the same was what's provided to the model. By default, providing items will enable virtualization.
This example adds a maxHeight
to ensure overflow. Virtualization uses absolute positioning of each
item, which could cause problems for popup menus. If your item count is low, pass
shouldVirtualize={false}
to disable virtualization.
- Item - 1
- Item - 2
- Item - 3
- Item - 4
DataLoader
The collection system comes with a data loader to help with dynamic collections. You must provide
total
, pageSize
, and an asynchronous load
function. The loader will call the load
function
when the user navigates the collection and needs more data. This example shows how to hook up a
simple data loader and mocks a load
function to simulate an asynchronous response.
The data loader also takes a model argument to know which model to create. The loader configures the model to handle asynchronous keyboard navigation and will return the configured model to you to pass along to the collection component.
Scroll or focus and use keys to navigate
Events:
Roving Tabindex
The list system also includes a cursor that extends the list. A cursor is mostly used for focusing
items. The roving tabindex is a
well-supported way to accomplish accessibility requirements for focusing items within a list. This
example shows how to use useListRovingFocus
. This example uses the ListBox
component, but the
default ListBox.Item
is very basic. We have two options, we can either pass additional
functionality via elemPropsHook
or by creating a new item using our elemProps hook primitives.
Both will be demonstrated. Creating a custom item is recommended if you create a custom component
and export it. Using elemPropsHook
with ListBox.Item
is recommended only for one-off instances.
You can either use the tab key for focus on an item or click on an item and then use the up/down
keys to navigation the list. By default, the list is set to wrap navigation using the
wrappingNavigationManager
. Only a single item in the list is a focus stop that "roves" as the
up/down arrows are pressed.
Note: This example doesn't meet accessibility requirements. The list will have to have some type of context. Like "navigation list" or "menu list".
- First
- Second
- Third
Selection
Lists support selection. useSelectionItem
is applied to an item which adds an onClick
that adds
the item to the state.selectedIds
. The default selection manager is a single select. This example
uses ListBox
and creates a custom SelectableItem
elemProps hook and component.
Cursor ID:
Selected ID: first
String Children
Sometimes it is desired to allow the string children to be the identifiers for each item. This could
be useful for autocomplete components where the item's text is the desired identifier. Normally, if
no data-id
is provided to the item, the system will choose the registration index. This would be
'0'
, '1'
, and so on. By passing useListItemAllowChildStrings
to an item component, it will
change this behavior to use the child text if no data-id
is provided.
Multiple Selection
The selection
manager can be passed directly to the model configuration to handle different
selection types. This example passes the multiSelectionManager
to handle selecting multiple items.
Cursor ID:
Selected IDs: first,second
Basic Grid
A grid is a two dimensional list. A columnCount
config is added to inform how to break up an array
of items. A grid is very similar to a list - it receives items as a single dimension list and uses
the columnCount
to determine keyboard navigation. Grids only support a single orientation.
Wrapping Grid
By default, navigating a grid does not wrap around when the user reaches the end of a row or column.
The grid model supports passing in a navigation manager. The collection system supports two types of
navigation managers, a non-wrapping navigationManager
and the wrappingNavigationManager
. The
following example passes the wrappingNavigationManager
manager to the model. Observe how the
cursor wraps around columns and rows when an edge of a column or row is encountered.
Component API
ListBox
ListBox
The ListBox
component that offers vertical rendering of a collection in the form of a
2-dimension list. It supports virtualization, rendering only visible items in the DOM while also
providing aria attributes to allow screen readers to still navigate virtual lists. The ListBox
contains a basic ListBox.Item
that renders list items that render correctly with virtualization
and adds aria-setsize
and aria-posinset
for screen readers.
The ListBox
is very basic and only adds enough functionality to render correctly. No additional
behaviors are added to navigate or select. React Hooks are provided to add this functionality and
are used by higher level components like Menu
and Menu.Item
which utilize ListBox
. The
ListBox
contains two Box
elements:
- Outer Box: Presentational container element responsible for overflow and height.
height
andmaxHeight
props will be applied here. - Inner Box: The element responsible for the virtual container. Height is controlled by the model and cannot be changed by the developer. All props and ref will be spread to this element.
Layout Component
ListBox
supports all props from thelayout component.
Props
Props extend from ul. Changing the as
prop will change the element interface.
Props extend from . If a model
is passed, props from ListModelConfig
are ignored.
Name | Type | Description | Default |
---|---|---|---|
children | ReactNode | (( | ||
as | React.ElementType | Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | ul |
ref | React.Ref | Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If | |
model |
| Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context. | |
elemPropsHook | ( | Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one. |
useListBox
(
model: ,
elemProps: {},
ref: React.Ref
) => {
style: {
position: 'relative';
height: number | undefined;
};
}
ListBox.Item
The ListBox.Item
is a simple placeholder for listbox items. The functionality of a
collection item varies between components. For example, a Tabs.Item
and a Menu.Item
have
shared functionality, but have different behavior. All collection-based components should
implement a custom Item
subcomponent using the collection-based behavior hooks. The Roving
Tabindex example uses the elemPropsHook
to provide additional
functionality. elemPropsHook
is provided on all compound components and is useful in the
example to add additional functionality without making a new component.
Layout Component
Item
supports all props from thelayout component.
Props
Props extend from li. Changing the as
prop will change the element interface.
Name | Type | Description | Default |
---|---|---|---|
children | ReactNode | ||
as | React.ElementType | Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | li |
ref | React.Ref | Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If | |
model |
| Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context. | |
elemPropsHook | ( | Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one. |
useListItemRegister
This elemProps hook is the base of all item component hooks. It registers an item with a
collection and sets the data-id
that is used by other hooks. It should always be the last
defined hook when using composeHooks
(composeHooks
executes hooks right to left and merges
props left to right). It is used by ListBox.Item
and all *.Item
subcomponents.
const useMyItem = composeHooks(useListItemSelect, // additional hooks go hereuseListItemRegister // always last);
(
model: ,
elemProps: {
data-id: string;
data-text: string;
children: ReactNode;
index: number;
disabled: boolean;
item: <any>;
virtual: ;
},
ref: React.Ref
) => {
ref: (instance: | null) => void;
data-id: string;
disabled: boolean | undefined;
item: null;
virtual: null;
aria-setsize: number | undefined;
aria-posinset: number | undefined;
style: ;
id: string;
}
Model
useListModel
The List model contains the the state and events necessary to track items, selection, and a cursor. Various hooks can be used for a List model to create common behaviors associated with lists, such as navigating a list with a keyboard, selection (single and multiple), and virtualization.
A list also has a "cursor". A cursor is often represented by focus, but it is not always a 1:1
mapping. Think of the cursor as the focus item within the list. If the list has browser focus, the
cursor will map to browser focus. Behaviors such as useListRovingFocus
will map the cursor to the
active tab stop of the list. For more information, see
Roving Tabindex. useListRovingFocus
adds keyboard events that map to navigation events. A Navigation Manager is
used to map new cursor ids to these events. The ListModel
takes an optional navigation
configuration to change the default navigation behavior. The default navigation manager is a
wrappingNavigationManager meaning the cursor will wrap around the
beginning and the ends. The cursor also provides a navigationManager that does
not wrap. This is the default navigation for grids.
The cursor also adds the concept of orientation
which defaults to 'vertical'
. A Tab list is an
example of a 'horizontal'
list.
const list = useListModel({// custom handling for selection. single and multi select are providedselection: mySelectionManager,// wrapping and non-wrapping navigation are providednavigation: myNavigationManager,items: [{ id: '1', text: 'First'}, { id: '2', text: 'Second' }],getId: item => item.id, // get the unique identifier of your item})
useListModel (config: ):
useGridModel
The Grid model extends the ListModel
and changes some config. For example, the columnCount
is
required on the grid model's configuration and orientation
is removed.
useGridModel (config: ):
Navigation Manager
NavigationManager
The list and grid models accept a navigation
config. If one is not provided, a default will be
chosen. It is possible to create a custom navigation manager to hand to the model if the default
doesn't work.
Name | Type | Description | Default |
---|---|---|---|
getFirst |
| Get the first item in a collection. This will be called when the | |
getLast |
| Get the last item in a collection. This will be called when the | |
getItem | ( | Get an item with the provided | |
getNext |
| Get the next item after the provided | |
getNextRow |
| For Grids: Get the cell in the next row from the provided | |
getPrevious |
| Get the previous item before the provided | |
getPreviousRow |
| For Grids: Get the cell in the previous row from the provided | |
getFirstOfRow |
| For Grids: Get the first item in a row. This will be called when the | |
getLastOfRow |
| For Grids: Get the last item in a row. This will be called when the | |
getNextPage |
| Get the next "page". A "page" is application specific and usually means next visible screen.
If the viewport is scrollable, it would scroll so that the last item visible is now the first
item visible. This is called when the | |
getPreviousPage |
| Get the next "page". A "page" is application specific and usually means previous visible
screen. If the viewport is scrollable, it would scroll so that the first item visible is now
the last item visible. This is called when the |
navigationManager
The navigationManager
implements the Navigation Manager interface for lists
and grids, but does not wrap when the user hits a boundary of the collection. This is the default
navigation manager for grids.
wrappingNavigationManager
The wrappingNavigationManager
implements the Navigation Manager interface
for lists and grids, and wraps when the user hits a boundary of the collection. Grids will wrap
columns, but not rows. This is the default navigation manager for lists.
Selection Manager
SelectionManager
The list and grid models accept a selection
config. If one is not provided,
singleSelectManager
is used. You can provide a custom select manager to suite your needs. A
selection manager is an object with a single select
method that takes an id and previously
selected ids and returns a new set of selected ids.
The collection system provides two selection managers: singleSelectManager
and
multiSelectionManager
.
Name | Type | Description | Default |
---|---|---|---|
select | ( | Sets a new |
Hooks
useListItemRegister
This elemProps hook is the base of all item component hooks. It registers an item with a
collection and sets the data-id
that is used by other hooks. It should always be the last
defined hook when using composeHooks
(composeHooks
executes hooks right to left and merges
props left to right). It is used by ListBox.Item
and all *.Item
subcomponents.
const useMyItem = composeHooks(useListItemSelect, // additional hooks go hereuseListItemRegister // always last);
(
model: ,
elemProps: {
data-id: string;
data-text: string;
children: ReactNode;
index: number;
disabled: boolean;
item: <any>;
virtual: ;
},
ref: React.Ref
) => {
ref: (instance: | null) => void;
data-id: string;
disabled: boolean | undefined;
item: null;
virtual: null;
aria-setsize: number | undefined;
aria-posinset: number | undefined;
style: ;
id: string;
}
useListItemAllowChildStrings
This elemProps hook allows for children values to be considered identifiers if the children are
strings. This can be useful for autocomplete or select components that allow string values. This
hook must be passed after because this hook sets the
data-id
attribute if one hasn't been defined by the application.
An example might look like:
const useMyListItem = composeHooks(// any other hooks hereuseListItemSelect,useListItemRegister,useListItemAllowChildStrings // always the last in the list)<MyList onSelect={({id}) => {console.log(id) // will be "First" or "Second"}}><MyList.Item>First</MyList.Item><MyList.Item>Second</MyList.Item></MyList>
(
model: ,
elemProps: {
data-id: string;
children: ReactNode;
},
ref: React.Ref
) => {
data-id: string | undefined;
}
useListItemRovingFocus
This elemProps hook is used for cursor navigation by using Roving
Tabindex. Only a single item in the
collection has a tab stop. Pressing an arrow key moves the tab stop to a different item in the
corresponding direction. See the Roving Tabindex example. This elemProps hook
should be applied to an *.Item
component.
const useMyItem = composeHooks(useListItemRovingFocus, // adds the roving tabindex supportuseListItemRegister);
(
model: ,
elemProps: {
data-id: string;
},
ref: React.Ref
) => {
onKeyDown: (event: ) => void;
onClick: () => void;
data-focus-id: string;
tabIndex: number;
}
useListItemSelect
This elemProps hook adds selection support to a *.Item
subcomponent of a collection. It adds a
click handler that toggles selection status according to the Selection
Manager used.
const useMyItem = composeHooks(useListItemSelect, // adds selection support to an itemuseListItemRegister);
(
model: ,
elemProps: {
data-id: string;
},
ref: React.Ref
) => {
onClick: (event: <>) => void;
}
useListRenderItems
This hook is meant to be used inside the render function of List
style components. It is used
by ListBox
. This hook gives list-based components their static and dynamic APIs to handle list
items. This hook should only be used if you want to implement your own List. For example,
Tabs.List
uses this hook, but Menu.List
uses ListBox
which uses this hook.
const MyList = createContainer('ul')({modelHook: useListModel,})((elemProps, Element, model) => {return <Element {...elemProps}>{useListRenderItems(model, elemProps.children)}</Element>;});
Props
Name | Type | Description | Default |
---|---|---|---|
state | { | ||
events | { | ||
getId | (item: ) => string |
useListResetCursorOnBlur
This elemProps hook resets the cursor when the list looses focus. By default,
useListItemRovingFocus will leave the last focused item as the
focusable item in the list. Sometimes it is desireable to reset the cursor to the last selected
item. For example, Tabs.Item
uses this hook to reset the tab stop to the currently selected tab.
const useMyItem = composeHooks(useListResetCursorOnBlur, // adds the cursor reset to selected for roving tabindexuseListItemRovingFocus,useListItemRegister);
(
model: ,
elemProps: {},
ref: React.Ref
) => {
onKeyDown: (event: ) => void;
onFocus: () => void;
onBlur: () => void;
}
useOverflowListItemMeasure
This elemProps hook measures a list item and reports it to an OverflowListModel
. This is used
in overflow detection.
const useMyItem = composeHooks(useOverflowListItemMeasure,useListRegisterItem,)
(
model: ,
elemProps: {
data-id: string;
},
ref: React.Ref
) => {
ref: (instance: | null) => void;
aria-hidden: true | undefined;
style: {};
}
useOverflowListMeasure
This elemProps hook measures a list and reports it to an OverflowListModel
. This is used in
overflow detection.
(
model: ,
elemProps: {},
ref: React.Ref
) => {
ref: (instance: | null) => void;
}
useOverflowListTarget
This elemProps hook measures an overflow list target and reports it to an OverflowListModel
.
This is used in overflow detection.
(
model: ,
elemProps: {},
ref: React.Ref
) => {
ref: (instance: | null) => void;
aria-hidden: boolean;
tabIndex: number;
style: {
position: string;
left: number;
} | undefined;
}
useListLoader
Create a data loader and a model. The list loader should be used on virtual data sets with possibly large amounts of data. The model should be passed to a collection component. The loader can be used to manipulate filters, sorters, and clear cache.
A simple loader using fetch could look like the following:
const {model, loader} = useListLoader({total: 1000,pageSize: 20,async load({pageNumber, pageSize}) {return fetch('/myUrl').then(response => response.json()).then(response => {return {total: response.total, items: response.items};});},},useListModel);
<T, M extends ((args: any[]) => any) & >(
config: <T, > & unknown {
"kind": "unknown",
"value": "unknown",
"text": "M['TConfig']"
},
modelHook: M
) => {
model: <M>;
loader: ;
}
Can't Find What You Need?
Check out our FAQ section which may help you find the information you're looking for.
FAQ Section