Contact List
A ListBox styled with Tailwind CSS, featuring sticky section headers and macOS-style multiple selection.
Example#
import {Collection, Header, ListBox, ListBoxItem, ListBoxSection, 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 (
<ListBoxSection>
<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>
</ListBoxSection>
);
}
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,
ListBoxSection,
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 (
<ListBoxSection>
<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>
</ListBoxSection>
);
}
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,
ListBoxSection,
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 (
<ListBoxSection>
<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>
</ListBoxSection>
);
}
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.