React AriaExamples

Contact List

A ListBox styled with Tailwind CSS, featuring sticky section headers and macOS-style multiple selection.

Example#


import {Collection, Header, ListBox, ListBoxItem, Section, Text} from 'react-aria-components';

function ContactListExample() {
  return (
    <div className="bg-gradient-to-r from-blue-500 to-sky-500 p-8 rounded-lg flex justify-center">
      <ListBox
        aria-label="Contacts"
        selectionMode="multiple"
        selectionBehavior="replace"
        className="w-72 max-h-[290px] overflow-auto outline-none bg-white text-gray-700 p-2 flex flex-col gap-2 rounded-lg shadow scroll-pb-2 scroll-pt-7"
      >
        <ContactSection title="Favorites" items={favorites}>
          {(item) => <Contact item={item} />}
        </ContactSection>
        <ContactSection title="All Contacts" items={people}>
          {(item) => <Contact item={item} />}
        </ContactSection>
      </ListBox>
    </div>
  );
}

function ContactSection({ title, children, items }) {
  return (
    <Section>
      <Header className="sticky -top-2 bg-white z-10 font-bold font-serif px-2 mb-1 text-slate-700">
        {title}
      </Header>
      <Collection items={items}>
        {children}
      </Collection>
    </Section>
  );
}

function Contact({ item }) {
  return (
    <ListBoxItem
      id={item.id}
      textValue={item.name}
      className="group relative py-1 px-2 outline-none cursor-default grid grid-rows-2 grid-flow-col auto-cols-max gap-x-3 rounded selected:bg-blue-500 text-slate-700 selected:text-white selected:[&:has(+[data-selected])]:rounded-b-none [&[data-selected]+[data-selected]]:rounded-t-none focus-visible:ring-2 ring-offset-2 ring-blue-500"
    >
      <img
        src={item.avatar}
        alt=""
        className="row-span-2 place-self-center h-8 w-8 rounded-full"
      />
      <Text slot="label" className="font-semibold truncate">{item.name}</Text>
      <Text
        slot="description"
        className="truncate text-sm text-slate-600 group-selected:text-white"
      >
        {item.username}
      </Text>
      <div className="absolute left-12 right-2 bottom-0 h-px bg-gray-200 group-selected:bg-blue-400 [.group[data-selected]:has(+:not([data-selected]))_&]:hidden [.group:not([data-selected]):has(+[data-selected])_&]:hidden [.group[data-selected]:last-child_&]:hidden" />
    </ListBoxItem>
  );
}
import {
  Collection,
  Header,
  ListBox,
  ListBoxItem,
  Section,
  Text
} from 'react-aria-components';

function ContactListExample() {
  return (
    <div className="bg-gradient-to-r from-blue-500 to-sky-500 p-8 rounded-lg flex justify-center">
      <ListBox
        aria-label="Contacts"
        selectionMode="multiple"
        selectionBehavior="replace"
        className="w-72 max-h-[290px] overflow-auto outline-none bg-white text-gray-700 p-2 flex flex-col gap-2 rounded-lg shadow scroll-pb-2 scroll-pt-7"
      >
        <ContactSection title="Favorites" items={favorites}>
          {(item) => <Contact item={item} />}
        </ContactSection>
        <ContactSection title="All Contacts" items={people}>
          {(item) => <Contact item={item} />}
        </ContactSection>
      </ListBox>
    </div>
  );
}

function ContactSection({ title, children, items }) {
  return (
    <Section>
      <Header className="sticky -top-2 bg-white z-10 font-bold font-serif px-2 mb-1 text-slate-700">
        {title}
      </Header>
      <Collection items={items}>
        {children}
      </Collection>
    </Section>
  );
}

function Contact({ item }) {
  return (
    <ListBoxItem
      id={item.id}
      textValue={item.name}
      className="group relative py-1 px-2 outline-none cursor-default grid grid-rows-2 grid-flow-col auto-cols-max gap-x-3 rounded selected:bg-blue-500 text-slate-700 selected:text-white selected:[&:has(+[data-selected])]:rounded-b-none [&[data-selected]+[data-selected]]:rounded-t-none focus-visible:ring-2 ring-offset-2 ring-blue-500"
    >
      <img
        src={item.avatar}
        alt=""
        className="row-span-2 place-self-center h-8 w-8 rounded-full"
      />
      <Text slot="label" className="font-semibold truncate">
        {item.name}
      </Text>
      <Text
        slot="description"
        className="truncate text-sm text-slate-600 group-selected:text-white"
      >
        {item.username}
      </Text>
      <div className="absolute left-12 right-2 bottom-0 h-px bg-gray-200 group-selected:bg-blue-400 [.group[data-selected]:has(+:not([data-selected]))_&]:hidden [.group:not([data-selected]):has(+[data-selected])_&]:hidden [.group[data-selected]:last-child_&]:hidden" />
    </ListBoxItem>
  );
}
import {
  Collection,
  Header,
  ListBox,
  ListBoxItem,
  Section,
  Text
} from 'react-aria-components';

function ContactListExample() {
  return (
    <div className="bg-gradient-to-r from-blue-500 to-sky-500 p-8 rounded-lg flex justify-center">
      <ListBox
        aria-label="Contacts"
        selectionMode="multiple"
        selectionBehavior="replace"
        className="w-72 max-h-[290px] overflow-auto outline-none bg-white text-gray-700 p-2 flex flex-col gap-2 rounded-lg shadow scroll-pb-2 scroll-pt-7"
      >
        <ContactSection
          title="Favorites"
          items={favorites}
        >
          {(item) => (
            <Contact
              item={item}
            />
          )}
        </ContactSection>
        <ContactSection
          title="All Contacts"
          items={people}
        >
          {(item) => (
            <Contact
              item={item}
            />
          )}
        </ContactSection>
      </ListBox>
    </div>
  );
}

function ContactSection(
  {
    title,
    children,
    items
  }
) {
  return (
    <Section>
      <Header className="sticky -top-2 bg-white z-10 font-bold font-serif px-2 mb-1 text-slate-700">
        {title}
      </Header>
      <Collection
        items={items}
      >
        {children}
      </Collection>
    </Section>
  );
}

function Contact(
  { item }
) {
  return (
    <ListBoxItem
      id={item.id}
      textValue={item
        .name}
      className="group relative py-1 px-2 outline-none cursor-default grid grid-rows-2 grid-flow-col auto-cols-max gap-x-3 rounded selected:bg-blue-500 text-slate-700 selected:text-white selected:[&:has(+[data-selected])]:rounded-b-none [&[data-selected]+[data-selected]]:rounded-t-none focus-visible:ring-2 ring-offset-2 ring-blue-500"
    >
      <img
        src={item.avatar}
        alt=""
        className="row-span-2 place-self-center h-8 w-8 rounded-full"
      />
      <Text
        slot="label"
        className="font-semibold truncate"
      >
        {item.name}
      </Text>
      <Text
        slot="description"
        className="truncate text-sm text-slate-600 group-selected:text-white"
      >
        {item.username}
      </Text>
      <div className="absolute left-12 right-2 bottom-0 h-px bg-gray-200 group-selected:bg-blue-400 [.group[data-selected]:has(+:not([data-selected]))_&]:hidden [.group:not([data-selected]):has(+[data-selected])_&]:hidden [.group[data-selected]:last-child_&]:hidden" />
    </ListBoxItem>
  );
}

Tailwind config#

This example uses the tailwindcss-react-aria-components plugin. Add it to your tailwind.config.js:

module.exports = {
  // ...
  plugins: [
    require('tailwindcss-react-aria-components')
  ]
};
module.exports = {
  // ...
  plugins: [
    require('tailwindcss-react-aria-components')
  ]
};
module.exports = {
  // ...
  plugins: [
    require(
      'tailwindcss-react-aria-components'
    )
  ]
};

Components#


ListBox
A listbox displays a list of options, and allows a user to select one or more of them.