alpha

CardView

CardViews display a collection of items in a variety of layouts, providing users with a visual representation of the collection's contents.

installyarn add @react-spectrum/card
version3.0.0-alpha.1
usageimport {Card, CardView, GalleryLayout, GridLayout, WaterfallLayout} from '@react-spectrum/card'

Example#


import {Content} from '@react-spectrum/view';
import {Heading, Text} from '@react-spectrum/text';
import {Image} from '@react-spectrum/image';

<CardView
  height="500px"
  maxWidth="800px"
  layout={GridLayout}
  aria-label="Static CardView Pokemon example"
>
  <Card key="1" width={475} height={475} textValue="Bulbasaur">
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png" />
    <Heading>Bulbasaur</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Grass, Poison</Content>
  </Card>
  <Card key="2" width={475} height={475} textValue="Ivysaur">
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png" />
    <Heading>Ivysaur</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Grass, Poison</Content>
  </Card>
  <Card key="3" width={475} height={475} textValue="Venusaur">
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/003.png" />
    <Heading>Venusaur</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Grass, Poison</Content>
  </Card>
  <Card key="4" width={475} height={475} textValue="Charmander">
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/004.png" />
    <Heading>Charmander</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Fire</Content>
  </Card>
  <Card key="5" width={475} height={475} textValue="Charmeleon">
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/005.png" />
    <Heading>Charmeleon</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Fire</Content>
  </Card>
  <Card key="6" width={475} height={475} textValue="Charizard">
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png" />
    <Heading>Charizard</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Fire, Flying</Content>
  </Card>
</CardView>
import {Content} from '@react-spectrum/view';
import {Heading, Text} from '@react-spectrum/text';
import {Image} from '@react-spectrum/image';

<CardView
  height="500px"
  maxWidth="800px"
  layout={GridLayout}
  aria-label="Static CardView Pokemon example"
>
  <Card
    key="1"
    width={475}
    height={475}
    textValue="Bulbasaur"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png" />
    <Heading>Bulbasaur</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Grass, Poison</Content>
  </Card>
  <Card
    key="2"
    width={475}
    height={475}
    textValue="Ivysaur"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png" />
    <Heading>Ivysaur</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Grass, Poison</Content>
  </Card>
  <Card
    key="3"
    width={475}
    height={475}
    textValue="Venusaur"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/003.png" />
    <Heading>Venusaur</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Grass, Poison</Content>
  </Card>
  <Card
    key="4"
    width={475}
    height={475}
    textValue="Charmander"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/004.png" />
    <Heading>Charmander</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Fire</Content>
  </Card>
  <Card
    key="5"
    width={475}
    height={475}
    textValue="Charmeleon"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/005.png" />
    <Heading>Charmeleon</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Fire</Content>
  </Card>
  <Card
    key="6"
    width={475}
    height={475}
    textValue="Charizard"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png" />
    <Heading>Charizard</Heading>
    <Text slot="detail">PNG</Text>
    <Content>Fire, Flying</Content>
  </Card>
</CardView>
import {Content} from '@react-spectrum/view';
import {
  Heading,
  Text
} from '@react-spectrum/text';
import {Image} from '@react-spectrum/image';

<CardView
  height="500px"
  maxWidth="800px"
  layout={GridLayout}
  aria-label="Static CardView Pokemon example"
>
  <Card
    key="1"
    width={475}
    height={475}
    textValue="Bulbasaur"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png" />
    <Heading>
      Bulbasaur
    </Heading>
    <Text slot="detail">
      PNG
    </Text>
    <Content>
      Grass, Poison
    </Content>
  </Card>
  <Card
    key="2"
    width={475}
    height={475}
    textValue="Ivysaur"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png" />
    <Heading>
      Ivysaur
    </Heading>
    <Text slot="detail">
      PNG
    </Text>
    <Content>
      Grass, Poison
    </Content>
  </Card>
  <Card
    key="3"
    width={475}
    height={475}
    textValue="Venusaur"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/003.png" />
    <Heading>
      Venusaur
    </Heading>
    <Text slot="detail">
      PNG
    </Text>
    <Content>
      Grass, Poison
    </Content>
  </Card>
  <Card
    key="4"
    width={475}
    height={475}
    textValue="Charmander"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/004.png" />
    <Heading>
      Charmander
    </Heading>
    <Text slot="detail">
      PNG
    </Text>
    <Content>
      Fire
    </Content>
  </Card>
  <Card
    key="5"
    width={475}
    height={475}
    textValue="Charmeleon"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/005.png" />
    <Heading>
      Charmeleon
    </Heading>
    <Text slot="detail">
      PNG
    </Text>
    <Content>
      Fire
    </Content>
  </Card>
  <Card
    key="6"
    width={475}
    height={475}
    textValue="Charizard"
  >
    <Image src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png" />
    <Heading>
      Charizard
    </Heading>
    <Text slot="detail">
      PNG
    </Text>
    <Content>
      Fire, Flying
    </Content>
  </Card>
</CardView>

Content#


CardView is a collection component that displays a collection of cards in a variety of layouts. It follows the Collection Components API, accepting both static and dynamic collections. It expects <Card> elements as children, which in turn accept a variety of other content elements as described here. Static collections, as in the previous example, can be used when the full list of cards is known ahead of time.

Dynamic collections, as shown below, can be used when the card data come from an external data source such as an API call, or update over time. As seen below, an iterable list of items is passed to the CardView using the items prop. Each card accepts a key prop, which is passed to the onSelectionChange handler to identify the selected card. Alternatively, if the item objects contain an id property, as shown in the example below, then this is used automatically and a key prop is not required. See the Selection section for more detail on selection.

let items = [
  {
    id: 1,
    name: 'Bulbasaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png'
  },
  {
    id: 2,
    name: 'Ivysaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png'
  },
  {
    id: 3,
    name: 'Venusaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/003.png'
  },
  {
    id: 4,
    name: 'Charmander',
    type: 'Fire',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/004.png'
  },
  {
    id: 5,
    name: 'Charmeleon',
    type: 'Fire',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/005.png'
  },
  {
    id: 6,
    name: 'Charizard',
    type: 'Fire, Flying',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png'
  }
];

<CardView
  items={items}
  height="500px"
  maxWidth="800px"
  layout={GridLayout}
  aria-label="Dynamic CardView Pokemon example"
>
  {(item) => (
    <Card textValue={item.name} width={item.width} height={item.height}>
      <Image src={item.src} />
      <Heading>
        {item.name}
      </Heading>
      <Text slot="detail">
        PNG
      </Text>
      <Content>{item.type}</Content>
    </Card>
  )}
</CardView>
let items = [
  {
    id: 1,
    name: 'Bulbasaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png'
  },
  {
    id: 2,
    name: 'Ivysaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png'
  },
  {
    id: 3,
    name: 'Venusaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/003.png'
  },
  {
    id: 4,
    name: 'Charmander',
    type: 'Fire',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/004.png'
  },
  {
    id: 5,
    name: 'Charmeleon',
    type: 'Fire',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/005.png'
  },
  {
    id: 6,
    name: 'Charizard',
    type: 'Fire, Flying',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png'
  }
];

<CardView
  items={items}
  height="500px"
  maxWidth="800px"
  layout={GridLayout}
  aria-label="Dynamic CardView Pokemon example"
>
  {(item) => (
    <Card
      textValue={item.name}
      width={item.width}
      height={item.height}
    >
      <Image src={item.src} />
      <Heading>{item.name}</Heading>
      <Text slot="detail">PNG</Text>
      <Content>{item.type}</Content>
    </Card>
  )}
</CardView>
let items = [
  {
    id: 1,
    name: 'Bulbasaur',
    type:
      'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png'
  },
  {
    id: 2,
    name: 'Ivysaur',
    type:
      'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png'
  },
  {
    id: 3,
    name: 'Venusaur',
    type:
      'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/003.png'
  },
  {
    id: 4,
    name: 'Charmander',
    type: 'Fire',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/004.png'
  },
  {
    id: 5,
    name: 'Charmeleon',
    type: 'Fire',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/005.png'
  },
  {
    id: 6,
    name: 'Charizard',
    type: 'Fire, Flying',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png'
  }
];

<CardView
  items={items}
  height="500px"
  maxWidth="800px"
  layout={GridLayout}
  aria-label="Dynamic CardView Pokemon example"
>
  {(item) => (
    <Card
      textValue={item
        .name}
      width={item
        .width}
      height={item
        .height}
    >
      <Image
        src={item.src}
      />
      <Heading>
      {item.name}
      </Heading>
      <Text slot="detail">
      PNG</Text>
      <Content>
        {item.type}
      </Content>
    </Card>
  )}
</CardView>

Cards#

A standard Card consists of a preview image and a title, and allows for a optional detail area next to the title and an optional content area immediately below the title. The image, title, and content area can be populated by providing a Image, Heading, and Content respectively as children to the Card. The detail area can be populated through a Text element with an accompanying slot="detail" prop. Please note that a Spectrum compliant Card should not contain focusable or interactive content within the content area and that the content itself should only display the most essential pieces of information. If you need to perform actions on a selected card(s), please use the ActionBar component in lieu of adding an action menu to the Card itself. See the Card Actions section for an example.

The properties that the Card supports can be found in the Props table below. Note that the height and width props refer to the image's height and width, not the Card's height and width. These values are used to calculate the proper positioning and size of each card within the CardView's container.

Internationalization#

To internationalize a CardView, all text content within the Cards should be localized. The aria-label provided to the CardView should also be localized any. For languages that are read right-to-left (e.g. Hebrew and Arabic), the layout of CardView is automatically flipped.

Layout#


CardView supports three different visual layouts: GridLayout, GalleryLayout and WaterfallLayout. You can set the CardView's layout by passing the imported class directly to the layout prop as shown in the previous examples. This will handle instantiating said layout with its default options. Alternatively, you can modify the layout's various options (e.g. margin, collator, etc) by constructing your own instance of the class and providing that to the CardView instead. See the sections below for examples of this.

GridLayout#

The GridLayout renders each card with a uniform height and width regardless of the image size. The customizable options available to the GridLayout are as follows:

// TODO figure out how to show a table for the GridLayoutOptions

The example below shows a CardView with a modified GridLayout that decreases the minimum required space between the cards and sets the maximum number of columns to 2.

import {Size} from '@react-stately/virtualizer';
import {useCollator} from '@react-aria/i18n';
import {useProvider} from '@react-spectrum/provider';

function CustomLayout(props) {
  let items = [
    {
      width: 1001,
      height: 381,
      src: 'https://i.imgur.com/Z7AzH2c.jpg',
      title: 'Temple Roof',
      ext: 'JPG'
    },
    {
      width: 640,
      height: 640,
      src: 'https://i.imgur.com/DhygPot.jpg',
      title: 'Starry Sky',
      ext: 'JPG'
    },
    {
      width: 1516,
      height: 1009,
      src: 'https://i.imgur.com/1nScMIH.jpg',
      title: 'Molten glass',
      ext: 'JPG'
    },
    {
      width: 1215,
      height: 121,
      src: 'https://i.imgur.com/zzwWogn.jpg',
      title: 'Mountain Range',
      ext: 'JPG'
    }
  ];

  let { scale } = useProvider();
  let collator = useCollator({ usage: 'search', sensitivity: 'base' });
  let gridLayout = React.useMemo(() =>
    new GridLayout({
      scale,
      collator,
      minSpace: new Size(5, 5),
      maxColumns: 2
    }), [collator, scale]);

  let {
    layout = gridLayout,
    ...otherProps
  } = props;

  return (
    <CardView
      items={items}
      layout={layout}
      maxWidth="800px"
      height="500px"
      aria-label="CardView with modified GridLayout"
      {...otherProps}
    >
      {(item) => (
        <Card
          key={item.title}
          textValue={item.title}
          width={item.width}
          height={item.height}
        >
          <Image src={item.src} />
          <Heading>{item.title}</Heading>
          <Text slot="detail">{item.ext}</Text>
          <Content>Image dimensions: {item.width}x{item.height}</Content>
        </Card>
      )}
    </CardView>
  );
}

<CustomLayout />
import {Size} from '@react-stately/virtualizer';
import {useCollator} from '@react-aria/i18n';
import {useProvider} from '@react-spectrum/provider';

function CustomLayout(props) {
  let items = [
    {
      width: 1001,
      height: 381,
      src: 'https://i.imgur.com/Z7AzH2c.jpg',
      title: 'Temple Roof',
      ext: 'JPG'
    },
    {
      width: 640,
      height: 640,
      src: 'https://i.imgur.com/DhygPot.jpg',
      title: 'Starry Sky',
      ext: 'JPG'
    },
    {
      width: 1516,
      height: 1009,
      src: 'https://i.imgur.com/1nScMIH.jpg',
      title: 'Molten glass',
      ext: 'JPG'
    },
    {
      width: 1215,
      height: 121,
      src: 'https://i.imgur.com/zzwWogn.jpg',
      title: 'Mountain Range',
      ext: 'JPG'
    }
  ];

  let { scale } = useProvider();
  let collator = useCollator({
    usage: 'search',
    sensitivity: 'base'
  });
  let gridLayout = React.useMemo(() =>
    new GridLayout({
      scale,
      collator,
      minSpace: new Size(5, 5),
      maxColumns: 2
    }), [collator, scale]);

  let {
    layout = gridLayout,
    ...otherProps
  } = props;

  return (
    <CardView
      items={items}
      layout={layout}
      maxWidth="800px"
      height="500px"
      aria-label="CardView with modified GridLayout"
      {...otherProps}
    >
      {(item) => (
        <Card
          key={item.title}
          textValue={item.title}
          width={item.width}
          height={item.height}
        >
          <Image src={item.src} />
          <Heading>{item.title}</Heading>
          <Text slot="detail">{item.ext}</Text>
          <Content>
            Image dimensions: {item.width}x{item.height}
          </Content>
        </Card>
      )}
    </CardView>
  );
}

<CustomLayout />
import {Size} from '@react-stately/virtualizer';
import {useCollator} from '@react-aria/i18n';
import {useProvider} from '@react-spectrum/provider';

function CustomLayout(
  props
) {
  let items = [
    {
      width: 1001,
      height: 381,
      src:
        'https://i.imgur.com/Z7AzH2c.jpg',
      title:
        'Temple Roof',
      ext: 'JPG'
    },
    {
      width: 640,
      height: 640,
      src:
        'https://i.imgur.com/DhygPot.jpg',
      title:
        'Starry Sky',
      ext: 'JPG'
    },
    {
      width: 1516,
      height: 1009,
      src:
        'https://i.imgur.com/1nScMIH.jpg',
      title:
        'Molten glass',
      ext: 'JPG'
    },
    {
      width: 1215,
      height: 121,
      src:
        'https://i.imgur.com/zzwWogn.jpg',
      title:
        'Mountain Range',
      ext: 'JPG'
    }
  ];

  let { scale } =
    useProvider();
  let collator =
    useCollator({
      usage: 'search',
      sensitivity: 'base'
    });
  let gridLayout = React
    .useMemo(
      () =>
        new GridLayout({
          scale,
          collator,
          minSpace:
            new Size(
              5,
              5
            ),
          maxColumns: 2
        }),
      [collator, scale]
    );

  let {
    layout = gridLayout,
    ...otherProps
  } = props;

  return (
    <CardView
      items={items}
      layout={layout}
      maxWidth="800px"
      height="500px"
      aria-label="CardView with modified GridLayout"
      {...otherProps}
    >
      {(item) => (
        <Card
          key={item
            .title}
          textValue={item
            .title}
          width={item
            .width}
          height={item
            .height}
        >
          <Image
            src={item
              .src}
          />
          <Heading>
          {item.title}
          </Heading>
          <Text slot="detail">
          {item.ext}
          </Text>
          <Content>
            Image
            dimensions:
            {' '}
            {item
              .width}x{item
              .height}
          </Content>
        </Card>
      )}
    </CardView>
  );
}

<CustomLayout />

GalleryLayout#

The GalleryLayout arranges each card in such a way that each card in a row shares the same height but has a width that accommodates the image's aspect ratio. The layout will attempt to take up all available space in the CardView and thus will distribute the cards in each row accordingly. The customizable options available to the GalleryLayout are as follows:

// TODO figure out how to show a table for the GalleryLayoutOptions

The example below shows a CardView with a modified GalleryLayout that adjusts the space between the cards and increases the ideal row height used in the layout calculations.

// Using the same CardView as above the GridLayout example, but applying a modified GalleryLayout instead
function CustomGalleryLayout() {
  let { scale } = useProvider();
  let collator = useCollator({ usage: 'search', sensitivity: 'base' });
  let galleryLayout = React.useMemo(() =>
    new GalleryLayout({
      collator,
      scale,
      itemSpacing: new Size(50, 18)
    }), [collator, scale]);

  return (
    <CustomLayout
      layout={galleryLayout}
      aria-label="CardView with modified GalleryLayout"
    />
  );
}

<CustomGalleryLayout />
// Using the same CardView as above the GridLayout example, but applying a modified GalleryLayout instead
function CustomGalleryLayout() {
  let { scale } = useProvider();
  let collator = useCollator({
    usage: 'search',
    sensitivity: 'base'
  });
  let galleryLayout = React.useMemo(
    () =>
      new GalleryLayout({
        collator,
        scale,
        itemSpacing: new Size(50, 18)
      }),
    [collator, scale]
  );

  return (
    <CustomLayout
      layout={galleryLayout}
      aria-label="CardView with modified GalleryLayout"
    />
  );
}

<CustomGalleryLayout />
// Using the same CardView as above the GridLayout example, but applying a modified GalleryLayout instead
function CustomGalleryLayout() {
  let { scale } =
    useProvider();
  let collator =
    useCollator({
      usage: 'search',
      sensitivity: 'base'
    });
  let galleryLayout =
    React.useMemo(
      () =>
        new GalleryLayout(
          {
            collator,
            scale,
            itemSpacing:
              new Size(
                50,
                18
              )
          }
        ),
      [collator, scale]
    );

  return (
    <CustomLayout
      layout={galleryLayout}
      aria-label="CardView with modified GalleryLayout"
    />
  );
}

<CustomGalleryLayout />

WaterfallLayout#

The WaterfallLayout organizes the cards into equal width columns but allows each card to have a variable height. Similarly to GalleryLayout, it attempts to accommodate the aspect ratio of each card's image. The customizable options available to the GalleryLayout are as follows:

// TODO figure out how to show a table for the WaterfallLayoutOptions

The example below shows a CardView with a modified WaterfallLayout adjusts the minimum required space between the cards and sets the maximum number of columns to 2.

// Using the same CardView as above the GridLayout example, but applying a modified WaterfallLayout instead
function CustomWaterfallLayout() {
  let { scale } = useProvider();
  let collator = useCollator({ usage: 'search', sensitivity: 'base' });
  let waterfallLayout = React.useMemo(() =>
    new WaterfallLayout({
      collator,
      scale,
      maxColumns: 2,
      minSpace: new Size(50, 50)
    }), [collator, scale]);

  return (
    <CustomLayout
      layout={waterfallLayout}
      aria-label="CardView with modified WaterfallLayout"
    />
  );
}

<CustomWaterfallLayout />
// Using the same CardView as above the GridLayout example, but applying a modified WaterfallLayout instead
function CustomWaterfallLayout() {
  let { scale } = useProvider();
  let collator = useCollator({
    usage: 'search',
    sensitivity: 'base'
  });
  let waterfallLayout = React.useMemo(
    () =>
      new WaterfallLayout({
        collator,
        scale,
        maxColumns: 2,
        minSpace: new Size(50, 50)
      }),
    [collator, scale]
  );

  return (
    <CustomLayout
      layout={waterfallLayout}
      aria-label="CardView with modified WaterfallLayout"
    />
  );
}

<CustomWaterfallLayout />
// Using the same CardView as above the GridLayout example, but applying a modified WaterfallLayout instead
function CustomWaterfallLayout() {
  let { scale } =
    useProvider();
  let collator =
    useCollator({
      usage: 'search',
      sensitivity: 'base'
    });
  let waterfallLayout =
    React.useMemo(
      () =>
        new WaterfallLayout(
          {
            collator,
            scale,
            maxColumns:
              2,
            minSpace:
              new Size(
                50,
                50
              )
          }
        ),
      [collator, scale]
    );

  return (
    <CustomLayout
      layout={waterfallLayout}
      aria-label="CardView with modified WaterfallLayout"
    />
  );
}

<CustomWaterfallLayout />

Labeling#


Accessibility#

An aria-label must be provided to the CardView for accessibility. If the CardView is labeled by a separate element, an aria-labelledby prop must be provided using the id of the labeling element instead.

Asynchronous loading#


CardView supports loading data asynchronously, and will display a progress circle reflecting the current load state, set by the loadingState prop. It also supports infinite scrolling to load more data on demand as the user scrolls, via the onLoadMore prop.

This example uses the useAsyncList hook to handle loading the data. See the docs for more information.

import {useAsyncList} from '@react-stately/data';

function AsyncLoadingCardView() {
  let imageLinks = React.useRef(null);
  let list = useAsyncList({
    async load({ signal, cursor }) {
      if (cursor) {
        cursor = cursor.replace(/^http:\/\//i, 'https://');
      }

      if (imageLinks.current == null) {
        let fullRes = await fetch(
          'https://rawcdn.githack.com/akabab/starwars-api/0.2.1/api/all.json'
        );
        imageLinks.current = await fullRes.json();
      }

      let items = [];
      let res = await fetch(
        cursor || 'https://swapi.py4e.com/api/people/?search',
        { signal }
      );
      let json = await res.json();
      items = json.results.map((element) => ({
        src: imageLinks.current.find((char) => char.name === element.name)
          ?.image,
        title: element.name,
        mass: element.mass
      }));

      return {
        items: items,
        cursor: json.next
      };
    }
  });

  return (
    <CardView
      items={list.items}
      onLoadMore={list.loadMore}
      loadingState={list.loadingState}
      layout={GridLayout}
      maxWidth="800px"
      height="500px"
      aria-label="Async Star Wars CardView"
    >
      {(item) => (
        <Card key={item.title} textValue={item.title}>
          <Image src={item.src} />
          <Heading>{item.title}</Heading>
          <Content>Mass: {item.mass}</Content>
        </Card>
      )}
    </CardView>
  );
}
import {useAsyncList} from '@react-stately/data';

function AsyncLoadingCardView() {
  let imageLinks = React.useRef(null);
  let list = useAsyncList({
    async load({ signal, cursor }) {
      if (cursor) {
        cursor = cursor.replace(/^http:\/\//i, 'https://');
      }

      if (imageLinks.current == null) {
        let fullRes = await fetch(
          'https://rawcdn.githack.com/akabab/starwars-api/0.2.1/api/all.json'
        );
        imageLinks.current = await fullRes.json();
      }

      let items = [];
      let res = await fetch(
        cursor ||
          'https://swapi.py4e.com/api/people/?search',
        { signal }
      );
      let json = await res.json();
      items = json.results.map((element) => ({
        src: imageLinks.current.find((char) =>
          char.name === element.name
        )?.image,
        title: element.name,
        mass: element.mass
      }));

      return {
        items: items,
        cursor: json.next
      };
    }
  });

  return (
    <CardView
      items={list.items}
      onLoadMore={list.loadMore}
      loadingState={list.loadingState}
      layout={GridLayout}
      maxWidth="800px"
      height="500px"
      aria-label="Async Star Wars CardView"
    >
      {(item) => (
        <Card key={item.title} textValue={item.title}>
          <Image src={item.src} />
          <Heading>
            {item.title}
          </Heading>
          <Content>Mass: {item.mass}</Content>
        </Card>
      )}
    </CardView>
  );
}
import {useAsyncList} from '@react-stately/data';

function AsyncLoadingCardView() {
  let imageLinks = React
    .useRef(null);
  let list =
    useAsyncList({
      async load(
        {
          signal,
          cursor
        }
      ) {
        if (cursor) {
          cursor = cursor
            .replace(
              /^http:\/\//i,
              'https://'
            );
        }

        if (
          imageLinks
            .current ==
            null
        ) {
          let fullRes =
            await fetch(
              'https://rawcdn.githack.com/akabab/starwars-api/0.2.1/api/all.json'
            );
          imageLinks
            .current =
              await fullRes
                .json();
        }

        let items = [];
        let res =
          await fetch(
            cursor ||
              'https://swapi.py4e.com/api/people/?search',
            { signal }
          );
        let json =
          await res
            .json();
        items = json
          .results.map((
            element
          ) => ({
            src:
              imageLinks
                .current
                .find(
                  (char) =>
                    char
                      .name ===
                      element
                        .name
                )?.image,
            title:
              element
                .name,
            mass:
              element
                .mass
          }));

        return {
          items: items,
          cursor:
            json.next
        };
      }
    });

  return (
    <CardView
      items={list.items}
      onLoadMore={list
        .loadMore}
      loadingState={list
        .loadingState}
      layout={GridLayout}
      maxWidth="800px"
      height="500px"
      aria-label="Async Star Wars CardView"
    >
      {(item) => (
        <Card
          key={item
            .title}
          textValue={item
            .title}
        >
          <Image
            src={item
              .src}
          />
          <Heading>
          {item.title}
          </Heading>
          <Content>Mass:
          {' '}
          {item.mass}
          </Content>
        </Card>
      )}
    </CardView>
  );
}

Selection#


By default, CardView doesn't allow card selection but this can be enabled using the selectionMode prop. Use defaultSelectedKeys to provide a default set of selected Cards. Note that the value of the selected keys must match the key prop of the card.

The example below enables multiple selection mode, and uses defaultSelectedKeys to select the Cards with keys 2 and 4.

let items = [
  {
    id: 1,
    name: 'Bulbasaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png'
  },
  {
    id: 2,
    name: 'Ivysaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png'
  },
  {
    id: 3,
    name: 'Venusaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/003.png'
  },
  {
    id: 4,
    name: 'Charmander',
    type: 'Fire',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/004.png'
  },
  {
    id: 5,
    name: 'Charmeleon',
    type: 'Fire',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/005.png'
  },
  {
    id: 6,
    name: 'Charizard',
    type: 'Fire, Flying',
    width: 475,
    height: 475,
    src: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png'
  }
];

<CardView
  items={items}
  height="500px"
  maxWidth="800px"
  layout={GridLayout}
  aria-label="Default selection CardView Pokemon example"
  selectionMode="multiple"
  defaultSelectedKeys={[2, 4]}
>
  {(item) => (
    <Card textValue={item.name} width={item.width} height={item.height}>
      <Image src={item.src} />
      <Heading>
        {item.name}
      </Heading>
      <Text slot="detail">
        PNG
      </Text>
      <Content>{item.type}</Content>
    </Card>
  )}
</CardView>
let items = [
  {
    id: 1,
    name: 'Bulbasaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png'
  },
  {
    id: 2,
    name: 'Ivysaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png'
  },
  {
    id: 3,
    name: 'Venusaur',
    type: 'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/003.png'
  },
  {
    id: 4,
    name: 'Charmander',
    type: 'Fire',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/004.png'
  },
  {
    id: 5,
    name: 'Charmeleon',
    type: 'Fire',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/005.png'
  },
  {
    id: 6,
    name: 'Charizard',
    type: 'Fire, Flying',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png'
  }
];

<CardView
  items={items}
  height="500px"
  maxWidth="800px"
  layout={GridLayout}
  aria-label="Default selection CardView Pokemon example"
  selectionMode="multiple"
  defaultSelectedKeys={[2, 4]}
>
  {(item) => (
    <Card
      textValue={item.name}
      width={item.width}
      height={item.height}
    >
      <Image src={item.src} />
      <Heading>{item.name}</Heading>
      <Text slot="detail">PNG</Text>
      <Content>{item.type}</Content>
    </Card>
  )}
</CardView>
let items = [
  {
    id: 1,
    name: 'Bulbasaur',
    type:
      'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png'
  },
  {
    id: 2,
    name: 'Ivysaur',
    type:
      'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png'
  },
  {
    id: 3,
    name: 'Venusaur',
    type:
      'Grass, Poison',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/003.png'
  },
  {
    id: 4,
    name: 'Charmander',
    type: 'Fire',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/004.png'
  },
  {
    id: 5,
    name: 'Charmeleon',
    type: 'Fire',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/005.png'
  },
  {
    id: 6,
    name: 'Charizard',
    type: 'Fire, Flying',
    width: 475,
    height: 475,
    src:
      'https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png'
  }
];

<CardView
  items={items}
  height="500px"
  maxWidth="800px"
  layout={GridLayout}
  aria-label="Default selection CardView Pokemon example"
  selectionMode="multiple"
  defaultSelectedKeys={[
    2,
    4
  ]}
>
  {(item) => (
    <Card
      textValue={item
        .name}
      width={item
        .width}
      height={item
        .height}
    >
      <Image
        src={item.src}
      />
      <Heading>
      {item.name}
      </Heading>
      <Text slot="detail">
      PNG</Text>
      <Content>
        {item.type}
      </Content>
    </Card>
  )}
</CardView>

Controlled selection#

To programmatically control card selection, use the selectedKeys prop paired with the onSelectionChange callback. The key prop from the selected card will be passed into the callback when the card is pressed, allowing you to update state accordingly.

Here is how you would control selection for the above example.

function PokemonCardView(props) {
  let items = [
    // Same items as in the pokemon examples...
  ];
  let [selectedKeys, setSelectedKeys] = React.useState(new Set([2]));

  return (
    <CardView
      items={items}
      height="500px"
      maxWidth="800px"
      layout={GridLayout}
      aria-label="Controlled selection CardView Pokemon example"
      selectionMode="multiple"
      selectedKeys={selectedKeys}
      onSelectionChange={setSelectedKeys}
      {...props}
    >
      {(item) => (
        <Card textValue={item.name} width={item.width} height={item.height}>
          <Image src={item.src} />
          <Heading>
            {item.name}
          </Heading>
          <Text slot="detail">
            PNG
          </Text>
          <Content>{item.type}</Content>
        </Card>
      )}
    </CardView>
  );
}
function PokemonCardView(props) {
  let items = [
    // Same items as in the pokemon examples...
  ];
  let [selectedKeys, setSelectedKeys] = React.useState(
    new Set([2])
  );

  return (
    <CardView
      items={items}
      height="500px"
      maxWidth="800px"
      layout={GridLayout}
      aria-label="Controlled selection CardView Pokemon example"
      selectionMode="multiple"
      selectedKeys={selectedKeys}
      onSelectionChange={setSelectedKeys}
      {...props}
    >
      {(item) => (
        <Card
          textValue={item.name}
          width={item.width}
          height={item.height}
        >
          <Image src={item.src} />
          <Heading>{item.name}</Heading>
          <Text slot="detail">PNG</Text>
          <Content>{item.type}</Content>
        </Card>
      )}
    </CardView>
  );
}
function PokemonCardView(
  props
) {
  let items = [
    // Same items as in the pokemon examples...
  ];
  let [
    selectedKeys,
    setSelectedKeys
  ] = React.useState(
    new Set([2])
  );

  return (
    <CardView
      items={items}
      height="500px"
      maxWidth="800px"
      layout={GridLayout}
      aria-label="Controlled selection CardView Pokemon example"
      selectionMode="multiple"
      selectedKeys={selectedKeys}
      onSelectionChange={setSelectedKeys}
      {...props}
    >
      {(item) => (
        <Card
          textValue={item
            .name}
          width={item
            .width}
          height={item
            .height}
        >
          <Image
            src={item
              .src}
          />
          <Heading>
          {item.name}
          </Heading>
          <Text slot="detail">
          PNG</Text>
          <Content>
            {item.type}
          </Content>
        </Card>
      )}
    </CardView>
  );
}

Single selection#

To limit users to selecting only a single card at a time, selectionMode can be set to single.

// Using the same CardView as above
<PokemonCardView selectionMode="single" />
// Using the same CardView as above
<PokemonCardView selectionMode="single" />
// Using the same CardView as above
<PokemonCardView selectionMode="single" />

Disallow empty selection#

CardView also supports a disallowEmptySelection prop which forces the user to have at least one Card in the CardView selected at all times. In this mode, if a single card is selected and the user presses it, it will not be deselected.

// Using the same CardView as above
<PokemonCardView disallowEmptySelection />
// Using the same CardView as above
<PokemonCardView disallowEmptySelection />
// Using the same CardView as above
<PokemonCardView
  disallowEmptySelection
/>

Disabled cards#

You can disable specific card by providing an array of keys to CardView via the disabledKeys prop. This will prevent cards from being selectable as shown in the example below.

// Using the same CardView as above
<PokemonCardView selectionMode="multiple" disabledKeys={[3]} />
// Using the same CardView as above
<PokemonCardView
  selectionMode="multiple"
  disabledKeys={[3]}
/>
// Using the same CardView as above
<PokemonCardView
  selectionMode="multiple"
  disabledKeys={[3]}
/>

Card actions#

A common use case is to allow a user to perform actions on one or more cards. The example below illustrates how one could use ActionBar to trigger an action on the currently selected card.

import {ActionBar, ActionBarContainer, Item} from '@react-spectrum/actionbar';
import Copy from '@spectrum-icons/workflow/Copy';
import Delete from '@spectrum-icons/workflow/Delete';
import Edit from '@spectrum-icons/workflow/Edit';

let items = [
  // Same items as in the pokemon examples...
];

function CardViewActions() {
  let [selectedKeys, setSelectedKeys] = React.useState(new Set([2]));

  return (
    <ActionBarContainer height="500px">
      <CardView
        items={items}
        selectedKeys={selectedKeys}
        onSelectionChange={setSelectedKeys}
        height="500px"
        maxWidth="800px"
        layout={GridLayout}
        aria-label="CardView Pokemon with ActionBar example"
        selectionMode="single"
      >
        {(item) => (
          <Card textValue={item.name} width={item.width} height={item.height}>
            <Image src={item.src} />
            <Heading>
              {item.name}
            </Heading>
            <Text slot="detail">
              PNG
            </Text>
            <Content>{item.type}</Content>
          </Card>
        )}
      </CardView>
      <ActionBar
        selectedItemCount={selectedKeys.size}
        onClearSelection={() => {
          setSelectedKeys(new Set());
        }}
        onAction={(key) =>
          alert(
            `Performing ${key} action on ${selectedKeys.keys().next().value}`
          )}
      >
        <Item key="edit">
          <Edit />
          <Text>Edit</Text>
        </Item>
        <Item key="copy">
          <Copy />
          <Text>Copy</Text>
        </Item>
        <Item key="delete">
          <Delete />
          <Text>Delete</Text>
        </Item>
      </ActionBar>
    </ActionBarContainer>
  );
}

<CardViewActions />
import {
  ActionBar,
  ActionBarContainer,
  Item
} from '@react-spectrum/actionbar';
import Copy from '@spectrum-icons/workflow/Copy';
import Delete from '@spectrum-icons/workflow/Delete';
import Edit from '@spectrum-icons/workflow/Edit';

let items = [
  // Same items as in the pokemon examples...
];

function CardViewActions() {
  let [selectedKeys, setSelectedKeys] = React.useState(
    new Set([2])
  );

  return (
    <ActionBarContainer height="500px">
      <CardView
        items={items}
        selectedKeys={selectedKeys}
        onSelectionChange={setSelectedKeys}
        height="500px"
        maxWidth="800px"
        layout={GridLayout}
        aria-label="CardView Pokemon with ActionBar example"
        selectionMode="single"
      >
        {(item) => (
          <Card
            textValue={item.name}
            width={item.width}
            height={item.height}
          >
            <Image src={item.src} />
            <Heading>{item.name}</Heading>
            <Text slot="detail">PNG</Text>
            <Content>{item.type}</Content>
          </Card>
        )}
      </CardView>
      <ActionBar
        selectedItemCount={selectedKeys.size}
        onClearSelection={() => {
          setSelectedKeys(new Set());
        }}
        onAction={(key) =>
          alert(
            `Performing ${key} action on ${selectedKeys.keys().next().value}`
          )}
      >
        <Item key="edit">
          <Edit />
          <Text>Edit</Text>
        </Item>
        <Item key="copy">
          <Copy />
          <Text>Copy</Text>
        </Item>
        <Item key="delete">
          <Delete />
          <Text>Delete</Text>
        </Item>
      </ActionBar>
    </ActionBarContainer>
  );
}

<CardViewActions />
import {
  ActionBar,
  ActionBarContainer,
  Item
} from '@react-spectrum/actionbar';
import Copy from '@spectrum-icons/workflow/Copy';
import Delete from '@spectrum-icons/workflow/Delete';
import Edit from '@spectrum-icons/workflow/Edit';

let items = [
  // Same items as in the pokemon examples...
];

function CardViewActions() {
  let [
    selectedKeys,
    setSelectedKeys
  ] = React.useState(
    new Set([2])
  );

  return (
    <ActionBarContainer height="500px">
      <CardView
        items={items}
        selectedKeys={selectedKeys}
        onSelectionChange={setSelectedKeys}
        height="500px"
        maxWidth="800px"
        layout={GridLayout}
        aria-label="CardView Pokemon with ActionBar example"
        selectionMode="single"
      >
        {(item) => (
          <Card
            textValue={item
              .name}
            width={item
              .width}
            height={item
              .height}
          >
            <Image
              src={item
                .src}
            />
            <Heading>
              {item.name}
            </Heading>
            <Text slot="detail">
              PNG
            </Text>
            <Content>
              {item.type}
            </Content>
          </Card>
        )}
      </CardView>
      <ActionBar
        selectedItemCount={selectedKeys
          .size}
        onClearSelection={() => {
          setSelectedKeys(
            new Set()
          );
        }}
        onAction={(key) =>
          alert(
            `Performing ${key} action on ${selectedKeys.keys().next().value}`
          )}
      >
        <Item key="edit">
          <Edit />
          <Text>Edit
          </Text>
        </Item>
        <Item key="copy">
          <Copy />
          <Text>Copy
          </Text>
        </Item>
        <Item key="delete">
          <Delete />
          <Text>Delete
          </Text>
        </Item>
      </ActionBar>
    </ActionBarContainer>
  );
}

<CardViewActions />

Props#


CardView props#

NameTypeDefaultDescription
layoutCardViewLayoutConstructor<T>CardViewLayout<T>The layout that the CardView should use. Determines the visual layout of the cards and contains information such as the collecion of items, loading state, keyboard delegate, etc. The available layouts include GridLayout, GalleryLayout, and WaterfallLayout. See the Layout section for more information.
childrenCollectionChildren<T>The contents of the collection.
cardOrientationOrientationThe orientation of the cards within the CardView.
isQuietbooleanWhether the cards in the CardView should be displayed with a quiet style. Note this option is only valid for the waterfall layout and doesn't affect the cards for the other layouts.
renderEmptyState() => JSX.ElementSets what the CardView should render when there is no content to display.
loadingStateLoadingStateThe current loading state of the CardView.
itemsIterable<T>Item objects in the collection.
disabledKeysIterable<Key>The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.
selectionModeSelectionModeThe type of selection that is allowed in the collection.
disallowEmptySelectionbooleanWhether the collection allows empty selection.
selectedKeys'all'Iterable<Key>The currently selected keys in the collection (controlled).
defaultSelectedKeys'all'Iterable<Key>The initial selected keys in the collection (uncontrolled).
Events
NameTypeDefaultDescription
onSelectionChange( (keys: Selection )) => anyHandler that is called when the selection changes.
onLoadMore() => anyHandler that is called when more items should be loaded, e.g. while scrolling near the bottom.
Layout
NameTypeDefaultDescription
flexResponsive<stringnumberboolean>When used in a flex layout, specifies how the element will grow or shrink to fit the space available. See MDN.
flexGrowResponsive<number>When used in a flex layout, specifies how the element will grow to fit the space available. See MDN.
flexShrinkResponsive<number>When used in a flex layout, specifies how the element will shrink to fit the space available. See MDN.
flexBasisResponsive<numberstring>When used in a flex layout, specifies the initial main size of the element. See MDN.
alignSelfResponsive<'auto''normal''start''end''center''flex-start''flex-end''self-start''self-end''stretch'>Overrides the alignItems property of a flex or grid container. See MDN.
justifySelfResponsive<'auto''normal''start''end''flex-start''flex-end''self-start''self-end''center''left''right''stretch'>Specifies how the element is justified inside a flex or grid container. See MDN.
orderResponsive<number>The layout order for the element within a flex or grid container. See MDN.
gridAreaResponsive<string>When used in a grid layout, specifies the named grid area that the element should be placed in within the grid. See MDN.
gridColumnResponsive<string>When used in a grid layout, specifies the column the element should be placed in within the grid. See MDN.
gridRowResponsive<string>When used in a grid layout, specifies the row the element should be placed in within the grid. See MDN.
gridColumnStartResponsive<string>When used in a grid layout, specifies the starting column to span within the grid. See MDN.
gridColumnEndResponsive<string>When used in a grid layout, specifies the ending column to span within the grid. See MDN.
gridRowStartResponsive<string>When used in a grid layout, specifies the starting row to span within the grid. See MDN.
gridRowEndResponsive<string>When used in a grid layout, specifies the ending row to span within the grid. See MDN.
Spacing
NameTypeDefaultDescription
marginResponsive<DimensionValue>The margin for all four sides of the element. See MDN.
marginTopResponsive<DimensionValue>The margin for the top side of the element. See MDN.
marginBottomResponsive<DimensionValue>The margin for the bottom side of the element. See MDN.
marginStartResponsive<DimensionValue>The margin for the logical start side of the element, depending on layout direction. See MDN.
marginEndResponsive<DimensionValue>The margin for the logical end side of an element, depending on layout direction. See MDN.
marginXResponsive<DimensionValue>The margin for both the left and right sides of the element. See MDN.
marginYResponsive<DimensionValue>The margin for both the top and bottom sides of the element. See MDN.
Sizing
NameTypeDefaultDescription
widthResponsive<DimensionValue>The width of the element. See MDN.
minWidthResponsive<DimensionValue>The minimum width of the element. See MDN.
maxWidthResponsive<DimensionValue>The maximum width of the element. See MDN.
heightResponsive<DimensionValue>The height of the element. See MDN.
minHeightResponsive<DimensionValue>The minimum height of the element. See MDN.
maxHeightResponsive<DimensionValue>The maximum height of the element. See MDN.
Positioning
NameTypeDefaultDescription
positionResponsive<'static''relative''absolute''fixed''sticky'>Specifies how the element is positioned. See MDN.
topResponsive<DimensionValue>The top position for the element. See MDN.
bottomResponsive<DimensionValue>The bottom position for the element. See MDN.
leftResponsive<DimensionValue>The left position for the element. See MDN. Consider using start instead for RTL support.
rightResponsive<DimensionValue>The right position for the element. See MDN. Consider using start instead for RTL support.
startResponsive<DimensionValue>The logical start position for the element, depending on layout direction. See MDN.
endResponsive<DimensionValue>The logical end position for the element, depending on layout direction. See MDN.
zIndexResponsive<number>The stacking order for the element. See MDN.
isHiddenResponsive<boolean>Hides the element.
Accessibility
NameTypeDefaultDescription
idstringThe element's unique identifier. See MDN.
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.
Advanced
NameTypeDefaultDescription
UNSAFE_classNamestringSets the CSS className for the element. Only use as a last resort. Use style props instead.
UNSAFE_styleCSSPropertiesSets inline style for the element. Only use as a last resort. Use style props instead.

Card props#

NameTypeDefaultDescription
childrenReactNodeRendered contents of the card. Note that focusable elements are not allowed within the card in a CardView.
textValuestringA string representation of the cards's contents, used for features like typeahead.
keyKeyA unique key for the card.
Sizing
NameTypeDefaultDescription
widthnumberThe raw width of the card's image.
heightnumberThe raw height of the card's image.
Accessibility
NameTypeDefaultDescription
aria-labelstringAn accessibility label for this card.

Visual options#


Horizontal Cards#

GridLayout is the only layout that currently supports horizontal cards. To enable horizontal cards, provide cardOrientation="horizontal" to the CardView and to your GridLayout instance if you are instantiating one.

<CustomLayout
  layout={GridLayout}
  aria-label="CardView with horizontal cards"
  height="400px"
  cardOrientation="horizontal"
/>
<CustomLayout
  layout={GridLayout}
  aria-label="CardView with horizontal cards"
  height="400px"
  cardOrientation="horizontal"
/>
<CustomLayout
  layout={GridLayout}
  aria-label="CardView with horizontal cards"
  height="400px"
  cardOrientation="horizontal"
/>

Quiet#

WaterfallLayout is the only layout that currently supports quiet cards. To enable quiet cards, provide isQuiet to the CardView.

<CustomLayout
  layout={WaterfallLayout}
  aria-label="CardView with quiet cards"
  isQuiet
/>
<CustomLayout
  layout={WaterfallLayout}
  aria-label="CardView with quiet cards"
  isQuiet
/>
<CustomLayout
  layout={WaterfallLayout}
  aria-label="CardView with quiet cards"
  isQuiet
/>

Empty state#

Use the renderEmptyState prop to customize what the CardView will display if there are no cards provided.

import {Content} from '@react-spectrum/view';
import {IllustratedMessage} from '@react-spectrum/illustratedmessage';
import NotFound from '@spectrum-icons/illustrations/NotFound';
import {Heading} from '@react-spectrum/text';

function renderEmptyState() {
  return (
    <IllustratedMessage>
      <NotFound />
      <Heading>No results</Heading>
      <Content>No results found</Content>
    </IllustratedMessage>
  );
}

<CardView
  aria-label="Example CardView for empty state"
  height="size-3000"
  layout={GridLayout}
  renderEmptyState={renderEmptyState}
>
  {[]}
</CardView>
import {Content} from '@react-spectrum/view';
import {IllustratedMessage} from '@react-spectrum/illustratedmessage';
import NotFound from '@spectrum-icons/illustrations/NotFound';
import {Heading} from '@react-spectrum/text';

function renderEmptyState() {
  return (
    <IllustratedMessage>
      <NotFound />
      <Heading>No results</Heading>
      <Content>No results found</Content>
    </IllustratedMessage>
  );
}

<CardView
  aria-label="Example CardView for empty state"
  height="size-3000"
  layout={GridLayout}
  renderEmptyState={renderEmptyState}
>
  {[]}
</CardView>
import {Content} from '@react-spectrum/view';
import {IllustratedMessage} from '@react-spectrum/illustratedmessage';
import NotFound from '@spectrum-icons/illustrations/NotFound';
import {Heading} from '@react-spectrum/text';

function renderEmptyState() {
  return (
    <IllustratedMessage>
      <NotFound />
      <Heading>
        No results
      </Heading>
      <Content>
        No results found
      </Content>
    </IllustratedMessage>
  );
}

<CardView
  aria-label="Example CardView for empty state"
  height="size-3000"
  layout={GridLayout}
  renderEmptyState={renderEmptyState}
>
  {[]}
</CardView>