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 items.

  • 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 item value that is the same as what's provided to the model.

Registering Items

Dynamic items can be passed in as an array of strings or objects via the items prop or the model config.

Array of Strings

Use strings when your data is a simple list of options. Each string value is used as the unique identifer.

  • Pizza
  • Chocolate
  • Cheeseburgers
Array of Objects

Use objects when your data contains additional information. Each object must have an id to ensure it's registered and searchable. Additional information such as icons or if the item is disabled can be included in the object and will be accessible in the render function.

  • Atlanta (United States)
  • Amsterdam (Europe)
  • Austin (United States)
  • Beaverton (United States)

Note: The id should match your text value for each item. This ensures proper selection of items in components like Select and Autocomplete. If your id is different from your text value, you'll have to add a translation layer.

Virtualization

By default, providing items dynamically 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.

    Selected: Nothing

    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 and maxHeight 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.

    NameTypeDescriptionDefault
    children ReactNode ((
        item: T,
        index: number
      ) => ReactNode)
    cs

    The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use instead.

    import {handleCsProp} from '@workday/canvas-kit-styling';
    import {mergeStyles} from '@workday/canvas-kit-react/layout';
    // ...
    // `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
    // function signature, but adds support for style props.
    return (
    <Element
    {...handleCsProp(elemProps, [
    myStyles,
    myModifiers({ size: 'medium' }),
    myVars({ backgroundColor: 'red' })
    ])}
    >
    {children}
    </Element>
    )
    asReact.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 React.forwardRefand spread extra props to a root element.

    Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

    ul
    refReact.Ref<R = ul>

    Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

    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(
      model: ,
      elemProps: TProps
    ) => HTML Attributes

    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.

    NameTypeDescriptionDefault
    cs

    The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use instead.

    import {handleCsProp} from '@workday/canvas-kit-styling';
    import {mergeStyles} from '@workday/canvas-kit-react/layout';
    // ...
    // `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
    // function signature, but adds support for style props.
    return (
    <Element
    {...handleCsProp(elemProps, [
    myStyles,
    myModifiers({ size: 'medium' }),
    myVars({ backgroundColor: 'red' })
    ])}
    >
    {children}
    </Element>
    )
    childrenReactNode
    asReact.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 React.forwardRefand spread extra props to a root element.

    Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

    li
    refReact.Ref<R = li>

    Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

    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(
      model: ,
      elemProps: TProps
    ) => HTML Attributes

    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 here
    useListItemRegister // 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 provided
    selection: mySelectionManager,
    // wrapping and non-wrapping navigation are provided
    navigation: 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: ):

    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.

    NameTypeDescriptionDefault
    getFirst

    Get the first item in a collection. This will be called when the Home key is pressed for Lists and Ctrl+Home for Grids.

    getLast

    Get the last item in a collection. This will be called when the End key is pressed for Lists and Ctrl+End for Grids.

    getItem(
      id: string,
      model: 
    ) => <>

    Get an item with the provided id.

    getNext

    Get the next item after the provided id. This will be called when the Right arrow key is pressed for RTL languages and when the Left arrow is pressed for LTR languages.

    getNextRow

    For Grids: Get the cell in the next row from the provided id. This will be called when the Down arrow is pressed.

    getPrevious

    Get the previous item before the provided id. This will be called when the Left arrow key is pressed for RTL languages and when the Right arrow is pressed for LTR languages.

    getPreviousRow

    For Grids: Get the cell in the previous row from the provided id. This will be called when the Up arrow is pressed.

    getFirstOfRow

    For Grids: Get the first item in a row. This will be called when the Home key is pressed.

    getLastOfRow

    For Grids: Get the last item in a row. This will be called when the End key is pressed.

    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 PageDown key is pressed

    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 PageUp key is pressed

    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.

    NameTypeDescriptionDefault
    select(
      id: string,
      prevState: 
    ) => 

    Sets a new Selection state based on the current ID passed and the previous state. Each selection manager can implement this differently. For example, a single select manager may unselect all other items and select only the passed in id. A multiselect manager may toggle the passed id.

    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 here
    useListItemRegister // 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 here
    useListItemSelect,
    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;
        item: <any>;
      },
      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 support
    useListItemRegister
    );
    (
      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 item
    useListItemRegister
    );
    (
      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

    NameTypeDescriptionDefault
    state{
      UNSTABLE_virtual: {
        virtualItems: [];
        totalSize: number;
        scrollToOffset: (
          index: number,
          options: 
        ) => void;
        scrollToIndex: (
          index: number,
          options: 
        ) => void;
        measure: () => void;
      };
      UNSTABLE_defaultItemHeight: number;
      containerRef: ;
      id: string;
      orientation:  'horizontal' 'vertical';
      indexRef: ;
      nonInteractiveIds: string[];
      isVirtualized: boolean;
      items: <>[];
    }
    events{
      registerItem: (data: {
        item: ;
        textValue: string;
      }) => void;
      unregisterItem: (data: {
        id: string;
      }) => void;
      updateItemHeight: (data: {
        value: number;
      }) => void;
    }
    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 tabindex
    useListItemRovingFocus,
    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