> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ListItem

> Compose consistent rows with icons, descriptions, indicators, and actions.

export const StoryEmbed = ({storyId, title, height = 120}) => <>
    <Tabs>
      <Tab title="Light">
        <iframe title={`${title} (light)`} src={`https://storybook.twenty.com/iframe.html?id=${storyId}&viewMode=story&globals=colorScheme:light`} width="100%" height={height} loading="lazy" allow="clipboard-write" style={{
  border: 0
}} />
      </Tab>
      <Tab title="Dark">
        <iframe title={`${title} (dark)`} src={`https://storybook.twenty.com/iframe.html?id=${storyId}&viewMode=story&globals=colorScheme:dark`} width="100%" height={height} loading="lazy" allow="clipboard-write" style={{
  border: 0
}} />
      </Tab>
    </Tabs>
    <a href={`https://storybook.twenty.com/?path=/story/${storyId}`}>
      Open in Storybook
    </a>
  </>;

`ListItem` supplies the visual layout shared by list and menu rows. It renders a `div` by default for passive content. Use [ListItemButton](/ui/components/navigation/list-item-button) for ordinary action rows. Compose `ListItem` with an anchor for navigation or with a custom semantic owner when needed. Use [Dropdown](/ui/components/navigation/dropdown) or [Select](/ui/primitives/input/select) when you need a complete popup or selection owner.

<StoryEmbed storyId="ui-navigation-listitem--display" title="ListItem example" height={100} />

## Usage

```tsx theme={null}
import { IconBell } from 'twenty-ui/icon';
import { ListItem } from 'twenty-ui/primitives/navigation';

export const NotificationRow = () => (
  <ListItem startIcon={<IconBell />}>Notifications</ListItem>
);
```

## Anatomy

`ListItem` exposes content slots through props. These slots arrange content inside one row; they are not separately exported components.

```text theme={null}
ListItem
├── startIcon
├── children
├── description
├── actions
└── endIcon
```

| Slot | Requirement | Purpose |
| - | - | - |
| `children` | Required for a visible row label | Supplies the main text. |
| `startIcon`, `endIcon` | Optional | Add supporting icons at either end of the row content. |
| `description` | Optional | Adds supporting text inline or at the end, according to `descriptionPlacement`. |
| `actions` | Optional | Adds trailing content, such as a separately focusable action. |
| `indicator`, `shortcut`, `hasSubmenu` | Optional | Configure built-in selection marks, shortcut hints, and the submenu indicator. |

The row supplies layout. `render` accepts an element or a callback that receives native props and visual state. Spread those props onto the actual root when using a callback. Native attributes, event handlers, `className`, `style`, and `ref` reach that root. For a row with independent action buttons, keep the row itself non-interactive so those buttons remain separate controls.

## Action row

Use [ListItemButton](/ui/components/navigation/list-item-button) to pair row styling with native button interaction. Pass `disabled` once to control both appearance and activation.

```tsx theme={null}
import { ListItemButton } from 'twenty-ui/components/navigation';
import { IconCopy } from 'twenty-ui/icon';

type DuplicateActionProps = {
  onDuplicate: () => void;
  disabled?: boolean;
};

export const DuplicateAction = ({
  onDuplicate,
  disabled = false,
}: DuplicateActionProps) => (
  <ListItemButton
    disabled={disabled}
    startIcon={<IconCopy />}
    description="Create a copy"
    onClick={onDuplicate}
  >
    Duplicate
  </ListItemButton>
);
```

A custom button owner can still compose `ListItem` through `render`. That owner supplies native focus, Enter and Space activation, and disabled behavior. `ListItem.disabled` supplies only the matching visual style. `shortcut` displays hints; your application registers the shortcuts.

<StoryEmbed storyId="ui-navigation-listitem--button-owner" title="Custom button composition" height={100} />

## Navigation and explicit links

Pass an anchor through `render` when the entire row navigates. Put anchor attributes, including `href`, `target`, and `rel`, on that element.

```tsx theme={null}
import { ListItem } from 'twenty-ui/primitives/navigation';

export const DocumentationLink = () => (
  <ListItem render={<a href="https://twenty.com/developers" />}>
    Typography documentation
  </ListItem>
);
```

<StoryEmbed storyId="ui-navigation-listitem--link-owner" title="Link row" height={100} />

String labels stay text, including URL-shaped strings. ListItem does not create links. To link part of a non-interactive row, supply an anchor as caller-owned content. Keep that row as a `div` to avoid nested links or buttons. String labels retain their truncation and overflow tooltip; custom content can use [OverflowingTextWithTooltip](/ui/primitives/typography/overflowing-text-with-tooltip) with an explicit rendered anchor.

Links do not have a native `disabled` attribute. If a destination is unavailable, the link owner must supply the appropriate semantics, tab order, and navigation policy. Styling it with `ListItem.disabled` alone does not prevent navigation.

## Popup item owner

Use `Dropdown.ActionItem` or `Dropdown.OptionItem` inside their popup owner. These parts already compose the ListItem layout and expose its visual slots. The popup owner supplies roles, focus, keyboard navigation, activation, and disabled behavior.

```tsx theme={null}
import { Button } from 'twenty-ui/primitives/input';
import { Dropdown } from 'twenty-ui/components/navigation';

type RecordActionsProps = { onDuplicate: () => void };

export const RecordActions = ({ onDuplicate }: RecordActionsProps) => (
  <Dropdown.Root type="menu">
    <Dropdown.Trigger render={<Button>Record actions</Button>} />
    <Dropdown.Content>
      <Dropdown.ActionItem onClick={onDuplicate}>
        Duplicate record
      </Dropdown.ActionItem>
      <Dropdown.ActionItem disabled>Unavailable action</Dropdown.ActionItem>
    </Dropdown.Content>
  </Dropdown.Root>
);
```

<StoryEmbed storyId="ui-components-dropdown--display" title="Dropdown item owner" height={420} />

## Native event policy

ListItem forwards native event objects and handlers. Clicks bubble normally, including clicks on trailing action controls. Put any intentional boundary on the interactive owner or caller's handler.

```tsx theme={null}
import { ListItem } from 'twenty-ui/primitives/navigation';

type OpenRecordProps = { onOpen: () => void };

export const OpenRecord = ({ onOpen }: OpenRecordProps) => (
  <ListItem
    render={<button type="button" />}
    onClick={(event) => {
      event.stopPropagation();
      onOpen();
    }}
  >
    Open record
  </ListItem>
);
```

The caller also owns any `preventDefault()` policy, such as preventing a link's navigation. Passing `onClick` does not add a role, make a `div` keyboard-operable, or change propagation.

## Settings row

Use [SettingsRow](/ui/components/settings/settings-row) for a labeled switch row. It composes `ListItem` and `Switch`, manages their accessible association, and uses the switch’s disabled state for the row’s appearance.

## Selection indicator

```tsx theme={null}
import { useState } from 'react';
import { ListItem } from 'twenty-ui/primitives/navigation';

export const ShowArchivedToggle = () => {
  const [selected, setSelected] = useState(false);

  return (
    <ListItem
      render={<button type="button" aria-pressed={selected} />}
      selected={selected}
      indicator="check"
      onClick={() => setSelected(!selected)}
    >
      Show archived records
    </ListItem>
  );
};
```

`selected`, `focused`, and `disabled` control row styling. They do not register actions, move DOM focus, add accessible roles or states, or suppress events. The `check` and `checkbox` indicators are decorative; use a real [Checkbox](/ui/primitives/input/checkbox) for a form control. Set `aria-pressed`, `aria-selected`, or other state semantics on the owner that needs them.

## Picker rows

Selection belongs to the parent. `ListItem` has no uncontrolled selection state: pass `selected` and update it from `onClick`. Use `indicator="check"` for a single choice and `indicator="checkbox"` for multiple choices. Both indicators are decorative; the interactive owner handles activation.

In an existing listbox, pass `role="option"` and `aria-selected`, and keep focus and arrow-key handling in the listbox. For a complete selection widget, use [Select](/ui/primitives/input/select). Standalone selection buttons can use `aria-pressed`, as in these examples.

### Single selection

Place an [Avatar](/ui/primitives/data-display/avatar) in `startIcon` to identify a record. Use `description` for supporting text, and `descriptionPlacement="end"` for a trailing value.

<StoryEmbed storyId="ui-navigation-listitem-picker--single-selection" title="Single selection rows" height={170} />

```tsx theme={null}
import { useState } from 'react';
import { Avatar } from 'twenty-ui/primitives/data-display';
import { ListItem } from 'twenty-ui/primitives/navigation';

export const AssigneeButtons = () => {
  const [assignee, setAssignee] = useState<string | null>(null);

  return (
    <div role="group" aria-label="Assignee">
      {['Alex Morgan', 'Sam Taylor'].map((name) => (
        <ListItem
          key={name}
          render={<button type="button" />}
          aria-pressed={assignee === name}
          selected={assignee === name}
          indicator="check"
          startIcon={<Avatar name={name} size="md" imageProps={{ alt: '' }} />}
          onClick={() => setAssignee(name)}
        >
          {name}
        </ListItem>
      ))}
    </div>
  );
};
```

### Multiple selection

Compose [Tag](/ui/primitives/data-display/tag) as the row content for tagged values, or put [ColorSample](/ui/primitives/data-display/color-sample) in `startIcon` for a color choice. A row can also contain plain text or an avatar.

<StoryEmbed storyId="ui-navigation-listitem-picker--multiple-selection" title="Multiple selection rows" height={170} />

```tsx theme={null}
import { useState } from 'react';
import { Tag } from 'twenty-ui/primitives/data-display';
import { ListItem } from 'twenty-ui/primitives/navigation';

export const TeammateButtons = () => {
  const [teammates, setTeammates] = useState<string[]>([]);

  const toggleTeammate = (name: string) => {
    setTeammates((current) =>
      current.includes(name)
        ? current.filter((teammate) => teammate !== name)
        : [...current, name],
    );
  };

  return (
    <div role="group" aria-label="Teammates">
      {['Alex Morgan', 'Sam Taylor'].map((name) => (
        <ListItem
          key={name}
          render={<button type="button" />}
          aria-pressed={teammates.includes(name)}
          selected={teammates.includes(name)}
          indicator="checkbox"
          onClick={() => toggleTeammate(name)}
        >
          <Tag color="blue">{name}</Tag>
        </ListItem>
      ))}
    </div>
  );
};
```

The checkbox indicator is not a form control and does not submit a value. Use [Checkbox](/ui/primitives/input/checkbox) when you need form participation. Set native `disabled` on the rendered button to prevent activation and remove it from the tab order. Also pass `ListItem.disabled` for its visual appearance.

## Slots

Use `startIcon` and `endIcon` for icons, `description` for supporting text, `actions` for trailing content, and `hasSubmenu` for a submenu indicator. `descriptionPlacement` accepts `inline` or `end`. `color="danger"` styles a destructive action. Avoid nesting interactive controls inside a row rendered as a button.

```tsx theme={null}
import { Button } from 'twenty-ui/primitives/input';
import { ListItem } from 'twenty-ui/primitives/navigation';

type ContactRowProps = { onOpen: () => void };

export const ContactRow = ({ onOpen }: ContactRowProps) => (
  <ListItem
    description="Account owner"
    actions={
      <Button
        type="button"
        aria-label="Open Alex Morgan's profile"
        variant="ghost"
        size="sm"
        onClick={onOpen}
      >
        Open
      </Button>
    }
  >
    Alex Morgan
  </ListItem>
);
```

## Labels

Use `shortcutJoinLabel` to customize the text between sequential shortcut steps. It defaults to `then`; an empty string removes the join text. The same prop is available on `Dropdown` item parts.

## Props

The reference is generated from the public component types. Native `div` attributes and handlers are accepted, including accessible names and native events. Put button- or anchor-specific attributes on the element supplied through `render`. Both `ref` and the rendered element's ref target the actual DOM root.

<ParamField body="actions" type="ReactNode">
  Trailing content, such as buttons, rendered after the content.
</ParamField>

<ParamField body="actionsVisibility" type="&#x22;always&#x22; | &#x22;hover&#x22;" default="hover">
  When trailing actions are visible: on hover and focus, or always.
</ParamField>

<ParamField body="color" type="&#x22;danger&#x22; | &#x22;neutral&#x22;" default="neutral">
  Color of the text and icons. `danger` marks a destructive action.
</ParamField>

<ParamField body="description" type="ReactNode">
  Supporting text, placed according to `descriptionPlacement`.
</ParamField>

<ParamField body="descriptionPlacement" type="&#x22;end&#x22; | &#x22;inline&#x22;" default="inline">
  Where the description renders: inline after the content or at the end of
  the row.
</ParamField>

<ParamField body="disabled" type="boolean" default="false">
  Applies disabled styling only. Set native disabled behavior and accessible semantics on the button, link, or popup item owner.
</ParamField>

<ParamField body="endIcon" type="ReactNode">
  Icon rendered after the content.
</ParamField>

<ParamField body="focused" type="boolean" default="false">
  Applies highlighted styling without moving DOM focus or handling keyboard navigation.
</ParamField>

<ParamField body="hasSubmenu" type="boolean" default="false">
  Shows a chevron indicating that the item opens a submenu.
</ParamField>

<ParamField body="indicator" type="&#x22;check&#x22; | &#x22;checkbox&#x22; | &#x22;none&#x22;" default="none">
  Selection indicator: a check icon after the content or a checkbox before
  it.
</ParamField>

<ParamField body="ref" type="Ref<HTMLElement>">
  Ref to the actual DOM root, including a composed native button or link.
</ParamField>

<ParamField body="render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ListItemState>">
  Composes the row with an element or render callback. The owner supplies interaction semantics, native attributes, and behavior.
</ParamField>

<ParamField body="selected" type="boolean" default="false">
  Applies selected styling and the configured decorative indicator. Selection state and accessible semantics belong to the owner.
</ParamField>

<ParamField body="shortcut" type="ShortcutDefinition">
  Keyboard shortcut keys displayed at the end of the row. Registering the
  shortcut is up to the application.
</ParamField>

<ParamField body="shortcutJoinLabel" type="string">
  Text between sequential shortcut steps. Defaults to `then`; pass an empty string to omit it.
</ParamField>

<ParamField body="startIcon" type="ReactNode">
  Icon rendered before the content.
</ParamField>

Pass `shortcut={['Mod', 'Enter']}` for simultaneous keys or `shortcut={[['G'], ['P']]}` for successive presses. See [Shortcut](/ui/primitives/typography/shortcut) for nested combinations and the shared string formatter.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.