{/* Copyright 2023 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License. */}

import {ExampleLayout} from '@react-spectrum/docs';
export default ExampleLayout;

import docs from 'docs:react-aria-components';
import {TypeLink} from '@react-spectrum/docs';
import styles from '@react-spectrum/docs/src/docs.css';
import ListBox from '@react-spectrum/docs/pages/assets/component-illustrations/ListBox.svg';
import ProgressCircle from '@react-spectrum/docs/pages/assets/component-illustrations/ProgressCircle.svg';
import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard';
import ChevronRight from '@spectrum-icons/workflow/ChevronRight';

---
keywords: [example, listbox, aria, accessibility, react, component]
type: component
image: image-grid.png
description: An async image gallery with selectable items, styled with Tailwind CSS.
---

# Image Grid

An async-loaded image gallery with selectable items built with the [ListBox](../ListBox.html) component, and styled with [Tailwind CSS](https://tailwindcss.com/).

## Example

```tsx import
import './tailwind.global.css';
```

```tsx example standalone
import {ListBox, ListBoxItem, Text, ProgressBar} from 'react-aria-components';
import {useAsyncList} from 'react-stately';
import {CheckCircle2} from 'lucide-react';

type Item = {
  user: { name: string },
  urls: { regular: string },
  alt_description: string
};

function ImageGridExample() {
  let list = useAsyncList<Item, number>({
    async load({ signal, cursor }) {
      let page = cursor || 1;
      let res = await fetch(
        `https://api.unsplash.com/photos?page=${page}&per_page=25&client_id=AJuU-FPh11hn7RuumUllp4ppT8kgiLS7LtOHp_sp4nc`,
        { signal }
      );
      let items = await res.json();
      return { items, cursor: page + 1 };
    }
  });

  let renderEmptyState = () => {
    if (list.isLoading) {
      return <ProgressCircle />;
    }
  };

  return (
    <div className="bg-linear-to-r from-sky-500 to-teal-500 p-2 sm:p-8 rounded-lg flex justify-center">
      <ListBox aria-label="Images" items={list.items} selectionMode="multiple" layout="grid" renderEmptyState={renderEmptyState} className="overflow-auto outline-hidden bg-white rounded-lg shadow-sm p-2 h-[350px] w-full max-w-[372px] grid grid-cols-3 gap-3 empty:flex">
        {item => (
          <ListBoxItem textValue={item.user.name} className="relative rounded-sm outline-hidden group cursor-default">
            <img src={item.urls.regular} alt={item.alt_description} className="h-[80px] w-full object-cover rounded-sm group-selected:ring-2 group-focus-visible:ring-4 group-selected:group-focus-visible:ring-4 ring-offset-2 ring-sky-600" />
            <Text slot="label" className="text-[11px] text-gray-700 font-semibold overflow-hidden text-ellipsis whitespace-nowrap max-w-full block mt-1">{item.user.name}</Text>
            <div className="absolute top-2 left-2 text-sky-800 rounded-full leading-0 bg-white border border-white border-solid hidden group-selected:block">
              <CheckCircle2 className="h-6 w-6" />
            </div>
          </ListBoxItem>
        )}
      </ListBox>
    </div>
  );
}

function ProgressCircle() {
  return (
    <ProgressBar aria-label="Loading…" isIndeterminate className="flex items-center justify-center w-full">
      <svg className="animate-spin h-5 w-5 text-sky-800" fill="none" viewBox="0 0 24 24">
        <circle className="opacity-25 stroke-current stroke-[4px]" cx="12" cy="12" r="10" />
        <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
      </svg>
    </ProgressBar>
  );
}
```

### Tailwind config

This example uses the [tailwindcss-react-aria-components](../styling.html#plugin) plugin. When using Tailwind v4, add it to your CSS:

```css render=false
@import "tailwindcss";
@plugin "tailwindcss-react-aria-components";
```

<details>

<summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Tailwind v3</summary>

When using Tailwind v3, add the plugin to your `tailwind.config.js` instead:

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

**Note**: When using Tailwind v3, install `tailwindcss-react-aria-components` version 1.x instead of 2.x.

</details>

## Components

<section className={styles.cardGroup} data-size="small">

<ExampleCard
  url="../ListBox.html"
  title="ListBox"
  description="A listbox displays a list of options, and allows a user to select one or more of them.">
  <ListBox style={{background: 'var(--anatomy-gray-100)', width: 'calc(100% - 20px)', padding: 10, maxHeight: 132}} />
</ExampleCard>

<ExampleCard
  url="../ProgressBar.html"
  title="ProgressBar"
  description="A progress bar shows progress of an operation over time.">
  <ProgressCircle />
</ExampleCard>

</section>
