Black Lives Matter.  Support the Equal Justice Initiative.
Skip to main content
Reakit
DocumentationNewsletter
GitHub
GitHub

Composite

Composite is a component that may contain navigable items represented by CompositeItem. It's inspired by the WAI-ARIA Composite Role and implements all the keyboard navigation mechanisms to ensure that there's only one tab stop for the whole Composite element. This means that it can behave as a roving tabindex or aria-activedescendant container.

Since this a very abstract component, it's recommended that you use more concrete ones like Menu, Toolbar, TabList and other derivative components, or build your own on top of Composite.

#Installation

npm install reakit

Learn more in Get started.

#Usage

In the most basic usage, Composite will work as a roving tabindex container that works on both directions. That is, it listens to all arrow keys. You can specify a direction by passing an orientation initial state to useCompositeState.

import React from "react";
import { useCompositeState, Composite, CompositeItem } from "reakit/Composite";

function Example() {
  const composite = useCompositeState();
  return (
    <Composite {...composite} role="toolbar" aria-label="My toolbar">
      <CompositeItem {...composite} onClick={() => alert("clicked")}>
        Item 1
      </CompositeItem>
      <CompositeItem {...composite}>Item 2</CompositeItem>
      <CompositeItem {...composite}>Item 3</CompositeItem>
    </Composite>
  );
}

#aria-activedescendant

Composite may work as an aria-activedescendant container by just setting the virtual initial state to true.

You can still attach event handlers to CompositeItem just like it were using the roving tabindex method. You don't need to change anything else to make it work.

The unstable_virtual prop is still experimental and may change in future patch and minor releases.

import React from "react";
import { useCompositeState, Composite, CompositeItem } from "reakit/Composite";

function Example() {
  const composite = useCompositeState({ unstable_virtual: true });
  return (
    <Composite {...composite} role="toolbar" aria-label="My toolbar">
      <CompositeItem {...composite} onClick={() => alert("clicked")}>
        Item 1
      </CompositeItem>
      <CompositeItem {...composite}>Item 2</CompositeItem>
      <CompositeItem {...composite}>Item 3</CompositeItem>
    </Composite>
  );
}

#Two-dimensional navigation

You can build a two-dimensional Composite by using CompositeGroup.

Item
Item
Item
Item
Item
Item
Item
Item
Item
Item
Item
Item
Item
Item
Item
import React from "react";
import {
  useCompositeState,
  Composite,
  CompositeGroup,
  CompositeItem,
} from "reakit/Composite";

function Grid(props) {
  return <Composite role="grid" {...props} />;
}

function GridRow(props) {
  return <CompositeGroup role="row" {...props} />;
}

function GridCell(props) {
  return <CompositeItem as="div" role="gridcell" {...props} />;
}

function Example() {
  const composite = useCompositeState({ wrap: true });
  return (
    <Grid {...composite} aria-label="My grid">
      <GridRow {...composite}>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
      </GridRow>
      <GridRow {...composite}>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
      </GridRow>
      <GridRow {...composite}>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
        <GridCell {...composite}>Item</GridCell>
      </GridRow>
    </Grid>
  );
}

#Performance

If you notice performance issues when rendering several CompositeItems, you can do the following:

  1. Pass an id prop to each CompositeItem.
  2. Memoize all non-primitive props that you're passing to CompositeItem, including event handlers (e.g. onClick) and the children prop.

CompositeItem will compare the passed id with composite.currentId and, if the other props haven't been changed, it'll only re-render if it's the previous or the current active item.

In the example below, focus on any item and keep pressed to see it smoothly changing focus. Then, unmemoize any non-primitive prop (like onClick or children) and do the same to see the difference yourself.

import React from "react";
import { useCompositeState, Composite, CompositeItem } from "reakit/Composite";

const items = Array.from({ length: 88 }).map((_, i) => `item-${i}`);

function Example() {
  const composite = useCompositeState({ loop: true });
  // Remove the React.useCallback call below to see the difference
  const onClick = React.useCallback((event) => {
    window.alert(event.currentTarget.id);
  }, []);
  // If children aren't primitive values (like strings), you can memoize them
  // with React.useCallback
  const children = React.useCallback(
    (itemProps) => <span {...itemProps}>👉</span>,
    []
  );
  return (
    <Composite {...composite} role="toolbar" aria-label="Performance">
      {items.map((id) => (
        <CompositeItem {...composite} key={id} id={id} onClick={onClick}>
          {children}
        </CompositeItem>
      ))}
    </Composite>
  );
}

#Accessibility

  • When virtual is set to false (default):

    • CompositemItem has tabindex set to 0 if it's the current element. Otherwise tabindex is set to -1.
  • When virtual is set to true:

    • Composite has tabindex set to 0 and has aria-activedescendant set to the id of the current CompositeItem.
    • CompositeItem has aria-selected set to true if it's the current element.
  • On one-dimensional composites:

    • moves focus to the previous CompositeItem if orientation is vertical or not defined.
    • moves focus to the next CompositeItem if orientation is vertical or not defined.
    • moves focus to the next CompositeItem if orientation is horizontal or not defined.
    • moves focus to the previous CompositeItem if orientation is horizontal or not defined.
    • Home or PageUp moves focus to the first CompositeItem.
    • End or PageDown moves focus to the last CompositeItem.
  • On two-dimensional composites:

    • moves focus to the CompositeItem above.
    • moves focus to the CompositeItem below.
    • moves focus to the next CompositeItem.
    • moves focus to the previous CompositeItem.
    • Home moves focus to the first CompositeItem in the row.
    • End moves focus to the last CompositeItem in the row.
    • PageUp moves focus to the first CompositeItem in the column.
    • PageDown moves focus to the last CompositeItem in the column.
    • Ctrl+Home moves focus to the first CompositeItem in the composite element.
    • Ctrl+End moves focus to the last CompositeItem in the composite element.

Learn more in Accessibility.

#Composition

Learn more in Composition.

#Props

#useCompositeState

  • baseId string

    ID that will serve as a base for all the items IDs.

  • unstable_virtual boolean

    If enabled, the composite element will act as an aria-activedescendant container instead of roving tabindex. DOM focus will remain on the composite while its items receive virtual focus.

  • rtl boolean

    Determines how next and previous functions will behave. If rtl is set to true, they will be inverted. You still need to set dir="rtl" on HTML/CSS.

  • orientation "horizontal" | "vertical" | undefined

    Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the orientation value determines which arrow keys can be used to move focus:

    • undefined: all arrow keys work.
    • horizontal: only left and right arrow keys work.
    • vertical: only up and down arrow keys work.

    It doesn't have any effect on two-dimensional composites.

  • currentId string | null | undefined

    The current focused item id.

    • undefined will automatically focus the first enabled composite item.
    • null will focus the composite container and users will be able to navigate out of it using arrow keys.
    • If currentId is initially set to null, the composite element itself will have focus and users will be able to navigate to it using arrow keys.
  • loop boolean | "horizontal" | "vertical"

    On one-dimensional composites:

    • true loops from the last item to the first item and vice-versa.
    • horizontal loops only if orientation is horizontal or not set.
    • vertical loops only if orientation is vertical or not set.
    • If currentId is initially set to null, the composite element will be focused in between the last and first items.

    On two-dimensional composites:

    • true loops from the last row/column item to the first item in the same row/column and vice-versa. If it's the last item in the last row, it moves to the first item in the first row and vice-versa.
    • horizontal loops only from the last row item to the first item in the same row.
    • vertical loops only from the last column item to the first item in the column row.
    • If currentId is initially set to null, vertical loop will have no effect as moving down from the last row or up from the first row will focus the composite element.
    • If wrap matches the value of loop, it'll wrap between the last item in the last row or column and the first item in the first row or column and vice-versa.
  • wrap boolean | "horizontal" | "vertical"

    If enabled, moving to the next item from the last one in a row or column will focus the first item in the next row or column and vice-versa.

    • true wraps between rows and columns.
    • horizontal wraps only between rows.
    • vertical wraps only between columns.
    • If loop matches the value of wrap, it'll wrap between the last item in the last row or column and the first item in the first row or column and vice-versa.

#Composite

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

12 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • baseId string

    ID that will serve as a base for all the items IDs.

  • unstable_virtual boolean

    If enabled, the composite element will act as an aria-activedescendant container instead of roving tabindex. DOM focus will remain on the composite while its items receive virtual focus.

  • orientation "horizontal" | "vertical" | undefined

    Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the orientation value determines which arrow keys can be used to move focus:

    • undefined: all arrow keys work.
    • horizontal: only left and right arrow keys work.
    • vertical: only up and down arrow keys work.

    It doesn't have any effect on two-dimensional composites.

  • currentId string | null | undefined

    The current focused item id.

    • undefined will automatically focus the first enabled composite item.
    • null will focus the composite container and users will be able to navigate out of it using arrow keys.
    • If currentId is initially set to null, the composite element itself will have focus and users will be able to navigate to it using arrow keys.
  • wrap boolean | "horizontal" | "vertical"

    If enabled, moving to the next item from the last one in a row or column will focus the first item in the next row or column and vice-versa.

    • true wraps between rows and columns.
    • horizontal wraps only between rows.
    • vertical wraps only between columns.
    • If loop matches the value of wrap, it'll wrap between the last item in the last row or column and the first item in the first row or column and vice-versa.
  • unstable_moves number

    Stores the number of moves that have been performed by calling move, next, previous, up, down, first or last.

  • groups Group[]

    Lists all the composite groups with their id and DOM ref. This state is automatically updated when registerGroup and unregisterGroup are called.

  • items Item[]

    Lists all the composite items with their id, DOM ref, disabled state and groupId if any. This state is automatically updated when registerItem and unregisterItem are called.

  • setCurrentId (value: SetStateAction<string | null | undefine...

    Sets currentId.

  • first () => void

    Moves focus to the first item.

  • last () => void

    Moves focus to the last item.

  • move (id: string | null) => void

    Moves focus to a given item ID.

#CompositeGroup

  • id string | undefined

    Same as the HTML attribute.

6 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • baseId string

    ID that will serve as a base for all the items IDs.

  • registerGroup (group: Group) => void

    Registers a composite group.

  • unregisterGroup (id: string) => void

    Unregisters a composite group.

  • currentId string | null | undefined

    The current focused item id.

    • undefined will automatically focus the first enabled composite item.
    • null will focus the composite container and users will be able to navigate out of it using arrow keys.
    • If currentId is initially set to null, the composite element itself will have focus and users will be able to navigate to it using arrow keys.
  • unstable_moves number

    Stores the number of moves that have been performed by calling move, next, previous, up, down, first or last.

  • items Item[]

    Lists all the composite items with their id, DOM ref, disabled state and groupId if any. This state is automatically updated when registerItem and unregisterItem are called.

#CompositeItem

  • disabled boolean | undefined

    Same as the HTML attribute.

  • focusable boolean | undefined

    When an element is disabled, it may still be focusable. It works similarly to readOnly on form elements. In this case, only aria-disabled will be set.

  • id string | undefined

    Same as the HTML attribute.

15 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • baseId string

    ID that will serve as a base for all the items IDs.

  • unstable_virtual boolean

    If enabled, the composite element will act as an aria-activedescendant container instead of roving tabindex. DOM focus will remain on the composite while its items receive virtual focus.

  • orientation "horizontal" | "vertical" | undefined

    Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the orientation value determines which arrow keys can be used to move focus:

    • undefined: all arrow keys work.
    • horizontal: only left and right arrow keys work.
    • vertical: only up and down arrow keys work.

    It doesn't have any effect on two-dimensional composites.

  • unstable_moves number

    Stores the number of moves that have been performed by calling move, next, previous, up, down, first or last.

  • currentId string | null | undefined

    The current focused item id.

    • undefined will automatically focus the first enabled composite item.
    • null will focus the composite container and users will be able to navigate out of it using arrow keys.
    • If currentId is initially set to null, the composite element itself will have focus and users will be able to navigate to it using arrow keys.
  • items Item[]

    Lists all the composite items with their id, DOM ref, disabled state and groupId if any. This state is automatically updated when registerItem and unregisterItem are called.

  • setCurrentId (value: SetStateAction<string | null | undefine...

    Sets currentId.

  • first () => void

    Moves focus to the first item.

  • last () => void

    Moves focus to the last item.

  • registerItem (item: Item) => void

    Registers a composite item.

  • unregisterItem (id: string) => void

    Unregisters a composite item.

  • next (unstable_allTheWay?: boolean | undefined) => void

    Moves focus to the next item.

  • previous (unstable_allTheWay?: boolean | undefined) => void

    Moves focus to the previous item.

  • up (unstable_allTheWay?: boolean | undefined) => void

    Moves focus to the item above.

  • down (unstable_allTheWay?: boolean | undefined) => void

    Moves focus to the item below.

#CompositeItemWidget

2 state props

These props are returned by the state hook. You can spread them into this component ({...state}) or pass them separately. You can also provide these props from your own state logic.

  • wrap boolean | "horizontal" | "vertical"

    If enabled, moving to the next item from the last one in a row or column will focus the first item in the next row or column and vice-versa.

    • true wraps between rows and columns.
    • horizontal wraps only between rows.
    • vertical wraps only between columns.
    • If loop matches the value of wrap, it'll wrap between the last item in the last row or column and the first item in the first row or column and vice-versa.
  • currentId string | null | undefined

    The current focused item id.

    • undefined will automatically focus the first enabled composite item.
    • null will focus the composite container and users will be able to navigate out of it using arrow keys.
    • If currentId is initially set to null, the composite element itself will have focus and users will be able to navigate to it using arrow keys.