---
url: https://docs.tailor.tech/app-shell/components/item-list.md
description: >-
  Compact icon-and-label lists for consistent navigation inside and outside the
  Sidebar
---

# ItemList

`ItemList` presents compact icon-and-label rows for navigation and actions. Use it for in-content navigation, tables of contents, or other lists that should share the Sidebar's list presentation without depending on its layout or providers.

It does not require `AppShell`, a router, or a Sidebar provider. Resource metadata, translations, permissions, and active-route matching remain the caller's responsibility.

## Import

```tsx
import { ItemList } from "@tailor-platform/app-shell";
```

## Basic Usage

```tsx
function BasicUsage() {
  const [view, setView] = useState("Overview");
  return (
    <>
      <ItemList.Root aria-label="Workspace">
        <ItemList.Item
          icon={<Home />}
          isActive={view === "Overview"}
          onClick={() => setView("Overview")}
        >
          Overview
        </ItemList.Item>
        <ItemList.Separator />
        <ItemList.Section title="Business">
          <ItemList.Item icon={<ShoppingCart />} onClick={() => setView("Orders")}>
            Orders
          </ItemList.Item>
          <ItemList.Group title="Products" icon={<Package />}>
            <ItemList.Item onClick={() => setView("All products")}>All products</ItemList.Item>
            <ItemList.Item onClick={() => setView("Categories")}>Categories</ItemList.Item>
          </ItemList.Group>
        </ItemList.Section>
      </ItemList.Root>
      <output>Current view: {view}</output>
    </>
  );
}
```

## Sub-components

| Sub-component        | Description                                                                      |
| -------------------- | -------------------------------------------------------------------------------- |
| `ItemList.Root`      | A semantic `ul`; accepts standard list props                                     |
| `ItemList.Item`      | A compact icon-and-label row containing a button or supplied interactive element |
| `ItemList.Group`     | A collapsible, indented child list; open by default                              |
| `ItemList.Section`   | A labeled, non-collapsible list whose items retain their normal indentation      |
| `ItemList.Separator` | A decorative divider that remains a valid list child                             |

## Items, Links, and Actions

`ItemList.Item` is for links and actions, not static rows. It requires `onClick`, `render`, or both; omitting both is a type error, including for a disabled Item. With `onClick`, it renders a `button` with `type="button"` by default. Supply an anchor or application link through `render` for navigation.

`render` replaces the interactive element inside the row, not the containing `li`. `children` supplies its label, and `icon` supplies an optional decorative leading icon. Standard button props, classes, attributes, and refs apply to the inner operation. A disabled Item is skipped by Tab and cancels clicks, including navigation through a supplied link.

```tsx
function LinksAndActions() {
  const [checks, setChecks] = useState(0);
  const handleCheckNotifications = () => {
    setChecks((count) => count + 1);
  };
  return (
    <>
      <ItemList.Root aria-label="Quick access">
        <ItemList.Item icon={<Home />} render={<a href="https://example.com/help" />}>
          Help center
        </ItemList.Item>
        <ItemList.Item icon={<Bell />} onClick={handleCheckNotifications}>
          Check notifications
        </ItemList.Item>
      </ItemList.Root>
      <output>Notification checks: {checks}</output>
    </>
  );
}
```

For application navigation, use `Link` or `NavLink` from `@tailor-platform/app-shell` inside an AppShell-managed page. Do not import routing APIs directly from `react-router` or create a separate router around the list. The routes in this example belong to the documentation app; use your application's route paths in your own pages.

```tsx
function AppShellRouting() {
  return (
    <ItemList.Root aria-label="Documentation pages">
      <ItemList.Item render={<Link to="/components/button" />}>Button documentation</ItemList.Item>
      <ItemList.Item render={<NavLink to="/components/item-list" end />}>
        ItemList documentation
      </ItemList.Item>
    </ItemList.Root>
  );
}
```

`NavLink` owns route matching and supplies `aria-current="page"` when matched; ItemList styles that attribute with the same emphasis as `isActive`. Use `end` when only the exact destination should be current. With a plain link, the caller supplies `aria-current="page"` when appropriate.

`isActive` remains a boolean for explicit visual emphasis, including local actions. It does not imply `aria-current`, selection state, a `listbox` role, or `aria-selected`. ItemList does not perform route matching.

### Item Props

| Prop        | Type                                   | Default | Description                                                        |
| ----------- | -------------------------------------- | ------- | ------------------------------------------------------------------ |
| `icon`      | `ReactNode`                            | —       | Optional decorative leading icon                                   |
| `children`  | `ReactNode`                            | —       | Compact row label                                                  |
| `render`    | `ReactElement`                         | —       | Replaces the inner button; required unless `onClick` is supplied   |
| `onClick`   | `MouseEventHandler<HTMLButtonElement>` | —       | Action handler; required unless `render` is supplied               |
| `isActive`  | `boolean`                              | `false` | Explicit visual emphasis without route matching or selection state |
| `disabled`  | `boolean`                              | —       | Disables clicks and removes the operation from the tab order       |
| `className` | `string`                               | —       | Classes applied to the inner operation                             |

Other standard button props, such as `title` and ARIA attributes, are forwarded to the inner operation. Put link-specific props such as `href`, `target`, and `rel` on the element passed to `render`.

Rows without an `icon` do not reserve icon space. To align a mixed list, explicitly pass `icon={<span />}` on a row that needs an empty icon slot. Lists with no icons keep their compact indentation.

## Groups and Heading Hierarchies

`expandOnClick` defaults to `true`: clicking a group's heading toggles its child list, whether or not `render` is supplied. `render` only replaces the heading's main operation; it does not determine the expansion behavior. `defaultOpen` defaults to `true`; set it to `false` to start collapsed. Groups can be nested.

Set `expandOnClick={false}` to separate the heading's main operation from expansion. This requires `onClick` or `render` so the heading does not become an inert button. A separate button, named by the group title, expands and collapses the group. Use this for linked headings so navigation does not toggle the list.

`onClick` applies only to the heading's main operation. Clicking a child Item or the separate expand button does not invoke it. `isActive` and `aria-current="page"` control emphasis on the main heading operation, not the separate expand button.

For a table of contents, use a linked Group for a parent heading and Items for its child headings. Keep the list data aligned with the actual heading hierarchy and anchor IDs in the document.

```tsx
function LinkedGroup() {
  return (
    <ItemList.Root aria-label="Contents">
      <ItemList.Group
        title="Installing"
        render={<a href="#installing" aria-label="Installing" />}
        expandOnClick={false}
      >
        <ItemList.Item render={<a href="#configuration" aria-label="Configuration" />}>
          Configuration
        </ItemList.Item>
      </ItemList.Group>
    </ItemList.Root>
  );
}
```

### Group Props

| Prop            | Type                                   | Default | Description                                                                                       |
| --------------- | -------------------------------------- | ------- | ------------------------------------------------------------------------------------------------- |
| `title`         | `ReactNode`                            | —       | Accessible group label, including text and optional decorations                                   |
| `icon`          | `ReactNode`                            | —       | Optional decorative leading icon                                                                  |
| `render`        | `ReactElement`                         | —       | Replaces the main heading operation without changing expansion behavior                           |
| `onClick`       | `MouseEventHandler<HTMLButtonElement>` | —       | Handler for the main heading operation only                                                       |
| `expandOnClick` | `boolean`                              | `true`  | Toggle from the heading; `false` requires `onClick` or `render` and adds a separate expand button |
| `isActive`      | `boolean`                              | `false` | Explicit visual emphasis for the heading operation                                                |
| `defaultOpen`   | `boolean`                              | `true`  | Initial uncontrolled expanded state                                                               |
| `children`      | `ReactNode`                            | —       | Nested Items, Groups, Sections, or Separators                                                     |

Other standard `li` props, including `className`, apply to the containing group item, not its heading operation. `onClick` is the exception described above.

A Group's rendered label names its heading operation, separate expand button, and child list. Keep meaningful text in a rich `title`; ReactNode content is not converted into a string. Meaningful details such as counts can be read with the label, while purely decorative elements should use `aria-hidden="true"`. An icon-only title needs screen-reader text. Do not put links or buttons inside the title, because the heading itself is interactive.

## Sections and Separators

Use `Group` for hierarchy and `Section` for flat classification. A Section accepts a resolved string `title` and `children`, keeps child rows at their normal indentation, and does not add an interactive heading. Other standard `li` props apply to its outer item.

Neither Group nor Section requires wrapping its children in another `ItemList.Root`. Use `ItemList.Separator` between logical runs of items; it is decorative and renders a list item rather than an invalid direct `div` child of `ul`.

## Styling and Accessibility

* Keep labels concise; they truncate in narrow containers.
* Use `aria-label` or `aria-labelledby` on Root when a list needs an accessible name.
* Native links and buttons retain their keyboard interactions. Group buttons announce their expanded state and support Enter and Space.
* Resolve translations before passing labels; ItemList does not translate them. Group titles can contain ReactNode content, while Section titles remain strings.
* ItemList is an ordinary list, not a menu or selection widget. It does not provide arrow-key selection, search, reordering, or Sidebar icon-rail behavior.
* Existing Sidebar components retain route metadata resolution, rail tooltips and flyouts, and drawer dismissal while sharing the underlying list presentation.
