CardView
CardViews display a collection of items in a variety of layouts, providing users with a visual representation of the collection's contents.
install | yarn add @react-spectrum/card |
---|---|
version | 3.0.0-alpha.1 |
usage | import {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="380px"
width="100%"
layout={GridLayout}
aria-label="Static CardView example"
>
<Card key="1" width={1024} height={683} textValue="Shaggy Horse">
<Image src="https://lfdanlu.github.io/docPhotos/photos/DSC00527.jpeg" />
<Heading>Shaggy Horse</Heading>
<Text slot="detail">JPEG</Text>
<Content>Size: 366 KB</Content>
</Card>
<Card key="2" width={1024} height={683} textValue="Lighthouse">
<Image src="https://lfdanlu.github.io/docPhotos/photos/DSC00591.jpeg" />
<Heading>Lighthouse</Heading>
<Text slot="detail">JPEG</Text>
<Content>Size: 182 KB</Content>
</Card>
<Card key="3" width={1024} height={683} textValue="Waterfall">
<Image src="https://lfdanlu.github.io/docPhotos/photos/DSC00827.jpeg" />
<Heading>Waterfall</Heading>
<Text slot="detail">JPEG</Text>
<Content>Size: 182 KB</Content>
</Card>
</CardView>
import {Content} from '@react-spectrum/view';
import {Heading, Text} from '@react-spectrum/text';
import {Image} from '@react-spectrum/image';
<CardView
height="380px"
width="100%"
layout={GridLayout}
aria-label="Static CardView example"
>
<Card
key="1"
width={1024}
height={683}
textValue="Shaggy Horse"
>
<Image src="https://lfdanlu.github.io/docPhotos/photos/DSC00527.jpeg" />
<Heading>Shaggy Horse</Heading>
<Text slot="detail">JPEG</Text>
<Content>Size: 366 KB</Content>
</Card>
<Card
key="2"
width={1024}
height={683}
textValue="Lighthouse"
>
<Image src="https://lfdanlu.github.io/docPhotos/photos/DSC00591.jpeg" />
<Heading>Lighthouse</Heading>
<Text slot="detail">JPEG</Text>
<Content>Size: 182 KB</Content>
</Card>
<Card
key="3"
width={1024}
height={683}
textValue="Waterfall"
>
<Image src="https://lfdanlu.github.io/docPhotos/photos/DSC00827.jpeg" />
<Heading>Waterfall</Heading>
<Text slot="detail">JPEG</Text>
<Content>Size: 182 KB</Content>
</Card>
</CardView>
import {Content} from '@react-spectrum/view';
import {
Heading,
Text
} from '@react-spectrum/text';
import {Image} from '@react-spectrum/image';
<CardView
height="380px"
width="100%"
layout={GridLayout}
aria-label="Static CardView example"
>
<Card
key="1"
width={1024}
height={683}
textValue="Shaggy Horse"
>
<Image src="https://lfdanlu.github.io/docPhotos/photos/DSC00527.jpeg" />
<Heading>
Shaggy Horse
</Heading>
<Text slot="detail">
JPEG
</Text>
<Content>
Size: 366 KB
</Content>
</Card>
<Card
key="2"
width={1024}
height={683}
textValue="Lighthouse"
>
<Image src="https://lfdanlu.github.io/docPhotos/photos/DSC00591.jpeg" />
<Heading>
Lighthouse
</Heading>
<Text slot="detail">
JPEG
</Text>
<Content>
Size: 182 KB
</Content>
</Card>
<Card
key="3"
width={1024}
height={683}
textValue="Waterfall"
>
<Image src="https://lfdanlu.github.io/docPhotos/photos/DSC00827.jpeg" />
<Heading>
Waterfall
</Heading>
<Text slot="detail">
JPEG
</Text>
<Content>
Size: 182 KB
</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 comes from an external data source such as an API call, or update over time. Providing the data in this way allows CardView to automatically cache the rendering of each item, which dramatically improves performance.
Each card has a unique key defined by the data. In the example below, the key
of each card is implicitly defined by the id property of the item object. See collections to learn more keys in dynamic collections.
let items = [
{
id: 1,
name: 'Shaggy Horse',
size: '366 KB',
width: 1024,
height: 683,
src: 'https://lfdanlu.github.io/docPhotos/photos/DSC00527.jpeg'
},
{
id: 2,
name: 'Lighthouse',
size: '182 KB',
width: 1024,
height: 683,
src: 'https://lfdanlu.github.io/docPhotos/photos/DSC00591.jpeg'
},
{
id: 3,
name: 'Waterfall',
size: '182 KB',
width: 1024,
height: 683,
src: 'https://lfdanlu.github.io/docPhotos/photos/DSC00827.jpeg'
}
];
<CardView
items={items}
height="380px"
width="100%"
layout={GridLayout}
aria-label="Dynamic CardView example"
>
{(item) => (
<Card textValue={item.name} width={item.width} height={item.height}>
<Image src={item.src} />
<Heading>
{item.name}
</Heading>
<Text slot="detail">
JPEG
</Text>
<Content>Size: {item.size}</Content>
</Card>
)}
</CardView>
let items = [
{
id: 1,
name: 'Shaggy Horse',
size: '366 KB',
width: 1024,
height: 683,
src:
'https://lfdanlu.github.io/docPhotos/photos/DSC00527.jpeg'
},
{
id: 2,
name: 'Lighthouse',
size: '182 KB',
width: 1024,
height: 683,
src:
'https://lfdanlu.github.io/docPhotos/photos/DSC00591.jpeg'
},
{
id: 3,
name: 'Waterfall',
size: '182 KB',
width: 1024,
height: 683,
src:
'https://lfdanlu.github.io/docPhotos/photos/DSC00827.jpeg'
}
];
<CardView
items={items}
height="380px"
width="100%"
layout={GridLayout}
aria-label="Dynamic CardView example"
>
{(item) => (
<Card
textValue={item.name}
width={item.width}
height={item.height}
>
<Image src={item.src} />
<Heading>{item.name}</Heading>
<Text slot="detail">JPEG</Text>
<Content>Size: {item.size}</Content>
</Card>
)}
</CardView>
let items = [
{
id: 1,
name: 'Shaggy Horse',
size: '366 KB',
width: 1024,
height: 683,
src:
'https://lfdanlu.github.io/docPhotos/photos/DSC00527.jpeg'
},
{
id: 2,
name: 'Lighthouse',
size: '182 KB',
width: 1024,
height: 683,
src:
'https://lfdanlu.github.io/docPhotos/photos/DSC00591.jpeg'
},
{
id: 3,
name: 'Waterfall',
size: '182 KB',
width: 1024,
height: 683,
src:
'https://lfdanlu.github.io/docPhotos/photos/DSC00827.jpeg'
}
];
<CardView
items={items}
height="380px"
width="100%"
layout={GridLayout}
aria-label="Dynamic CardView example"
>
{(item) => (
<Card
textValue={item
.name}
width={item
.width}
height={item
.height}
>
<Image
src={item.src}
/>
<Heading>
{item.name}
</Heading>
<Text slot="detail">
JPEG</Text>
<Content>
Size:{' '}
{item.size}
</Content>
</Card>
)}
</CardView>
Cards#
A standard Card consists of a preview image and a title, and allows for an 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 an 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 card, 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 the layout class with its default options. Alternatively, you can modify the layout's various options (e.g. margins, item sizes, etc) by constructing your own instance of the
class and providing that to the CardView instead.
The example below demonstrates the differences between each of the layouts as well as showing how you could instantiate a layout instance.
import {ActionGroup, Item} from '@react-spectrum/actiongroup';
import {Flex} from '@react-spectrum/layout';
import {useProvider} from '@react-spectrum/provider';
function LayoutDemo() {
let items = [
{
width: 1001,
height: 381,
src: 'https://i.imgur.com/Z7AzH2c.jpg',
title: 'Temple Roof',
ext: 'JPG'
}
// ...
];
let { scale } = useProvider();
let gridLayout = React.useMemo(() => new GridLayout({ scale }), [scale]);
let galleryLayout = React.useMemo(() => new GalleryLayout({ scale }), [
scale
]);
let waterfallLayout = React.useMemo(() => new WaterfallLayout({ scale }), [
scale
]);
let [layout, setLayout] = React.useState(gridLayout);
let onAction = (key) => {
if (key === 'grid') {
setLayout(gridLayout);
} else if (key === 'gallery') {
setLayout(galleryLayout);
} else {
setLayout(waterfallLayout);
}
};
return (
<Flex direction="column" height="600px" width="100%">
<ActionGroup
defaultSelectedKeys={['grid']}
selectionMode="single"
disallowEmptySelection
onAction={onAction}
aria-label="CardView layout selection"
>
<Item key="grid">Grid layout</Item>
<Item key="gallery">Gallery layout</Item>
<Item key="waterfall">Waterfall layout</Item>
</ActionGroup>
<CardView
items={items}
layout={layout}
width="100%"
height="100%"
aria-label="CardView with changeable layout"
>
{(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>
</Flex>
);
}
import {
ActionGroup,
Item
} from '@react-spectrum/actiongroup';
import {Flex} from '@react-spectrum/layout';
import {useProvider} from '@react-spectrum/provider';
function LayoutDemo() {
let items = [
{
width: 1001,
height: 381,
src: 'https://i.imgur.com/Z7AzH2c.jpg',
title: 'Temple Roof',
ext: 'JPG'
}
// ...
];
let { scale } = useProvider();
let gridLayout = React.useMemo(
() => new GridLayout({ scale }),
[scale]
);
let galleryLayout = React.useMemo(
() => new GalleryLayout({ scale }),
[scale]
);
let waterfallLayout = React.useMemo(
() => new WaterfallLayout({ scale }),
[scale]
);
let [layout, setLayout] = React.useState(gridLayout);
let onAction = (key) => {
if (key === 'grid') {
setLayout(gridLayout);
} else if (key === 'gallery') {
setLayout(galleryLayout);
} else {
setLayout(waterfallLayout);
}
};
return (
<Flex direction="column" height="600px" width="100%">
<ActionGroup
defaultSelectedKeys={['grid']}
selectionMode="single"
disallowEmptySelection
onAction={onAction}
aria-label="CardView layout selection"
>
<Item key="grid">
Grid layout
</Item>
<Item key="gallery">
Gallery layout
</Item>
<Item key="waterfall">Waterfall layout</Item>
</ActionGroup>
<CardView
items={items}
layout={layout}
width="100%"
height="100%"
aria-label="CardView with changeable layout"
>
{(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>
</Flex>
);
}
import {
ActionGroup,
Item
} from '@react-spectrum/actiongroup';
import {Flex} from '@react-spectrum/layout';
import {useProvider} from '@react-spectrum/provider';
function LayoutDemo() {
let items = [
{
width: 1001,
height: 381,
src:
'https://i.imgur.com/Z7AzH2c.jpg',
title:
'Temple Roof',
ext: 'JPG'
}
// ...
];
let { scale } =
useProvider();
let gridLayout = React
.useMemo(
() =>
new GridLayout({
scale
}),
[scale]
);
let galleryLayout =
React.useMemo(
() =>
new GalleryLayout(
{ scale }
),
[scale]
);
let waterfallLayout =
React.useMemo(
() =>
new WaterfallLayout(
{ scale }
),
[scale]
);
let [
layout,
setLayout
] = React.useState(
gridLayout
);
let onAction = (
key
) => {
if (key === 'grid') {
setLayout(
gridLayout
);
} else if (
key === 'gallery'
) {
setLayout(
galleryLayout
);
} else {
setLayout(
waterfallLayout
);
}
};
return (
<Flex
direction="column"
height="600px"
width="100%"
>
<ActionGroup
defaultSelectedKeys={[
'grid'
]}
selectionMode="single"
disallowEmptySelection
onAction={onAction}
aria-label="CardView layout selection"
>
<Item key="grid">
Grid layout
</Item>
<Item key="gallery">
Gallery layout
</Item>
<Item key="waterfall">
Waterfall
layout
</Item>
</ActionGroup>
<CardView
items={items}
layout={layout}
width="100%"
height="100%"
aria-label="CardView with changeable layout"
>
{(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>
</Flex>
);
}
GridLayout#
As can be seen above, the GridLayout renders each card with a uniform height and width regardless of the image size. The customizable options specific to the GridLayout are as follows:
maxItemSize
minSpace
maxColumns
cardOrientation
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:
idealRowHeight
itemSpacing
threshold
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 specific to the WaterfallLayout are as follows:
maxItemSize
minSpace
maxColumns
Shared layout options#
In addition to the layout specific options mentioned above, each of the layouts possess following shared options:
minItemSize
itemPadding
scale
margin
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';
async function getImageData(page) {
// Mock function for getting paged data...
}
function AsyncLoadingCardView() {
let list = useAsyncList({
async load({ cursor }) {
let res = await getImageData(cursor || 1);
return {
items: res.result,
cursor: res.cursor
};
}
});
return (
<CardView
items={list.items}
onLoadMore={list.loadMore}
loadingState={list.loadingState}
layout={GridLayout}
width="100%"
height="500px"
aria-label="Async loading CardView"
>
{(item) => (
<Card key={item.title} textValue={item.title}>
<Image src={item.src} />
<Heading>{item.name}</Heading>
<Text slot="detail">{item.ext}</Text>
<Content>Size: {item.size}</Content>
</Card>
)}
</CardView>
);
}
<AsyncLoadingCardView />
import {useAsyncList} from '@react-stately/data';
async function getImageData(page) {
// Mock function for getting paged data...
}
function AsyncLoadingCardView() {
let list = useAsyncList({
async load({ cursor }) {
let res = await getImageData(cursor || 1);
return {
items: res.result,
cursor: res.cursor
};
}
});
return (
<CardView
items={list.items}
onLoadMore={list.loadMore}
loadingState={list.loadingState}
layout={GridLayout}
width="100%"
height="500px"
aria-label="Async loading CardView"
>
{(item) => (
<Card key={item.title} textValue={item.title}>
<Image src={item.src} />
<Heading>{item.name}</Heading>
<Text slot="detail">{item.ext}</Text>
<Content>Size: {item.size}</Content>
</Card>
)}
</CardView>
);
}
<AsyncLoadingCardView />
import {useAsyncList} from '@react-stately/data';
async function getImageData(
page
) {
// Mock function for getting paged data...
}
function AsyncLoadingCardView() {
let list =
useAsyncList({
async load(
{ cursor }
) {
let res =
await getImageData(
cursor || 1
);
return {
items:
res.result,
cursor:
res.cursor
};
}
});
return (
<CardView
items={list.items}
onLoadMore={list
.loadMore}
loadingState={list
.loadingState}
layout={GridLayout}
width="100%"
height="500px"
aria-label="Async loading CardView"
>
{(item) => (
<Card
key={item
.title}
textValue={item
.title}
>
<Image
src={item
.src}
/>
<Heading>
{item.name}
</Heading>
<Text slot="detail">
{item.ext}
</Text>
<Content>Size:
{' '}
{item.size}
</Content>
</Card>
)}
</CardView>
);
}
<AsyncLoadingCardView />
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: 'Icy Peak',
size: '206 KB',
width: 670,
height: 1005,
src: 'https://lfdanlu.github.io/docPhotos/photos/DSC00888.jpeg'
},
{
id: 2,
name: 'Grassy Plain',
size: '233 KB',
width: 1024,
height: 683,
src: 'https://lfdanlu.github.io/docPhotos/photos/DSC01051.jpeg'
}
// ...
];
<CardView
items={items}
height="500px"
width="100%"
layout={GridLayout}
aria-label="Default selection CardView 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">
JPEG
</Text>
<Content>Size: {item.size}</Content>
</Card>
)}
</CardView>
let items = [
{
id: 1,
name: 'Icy Peak',
size: '206 KB',
width: 670,
height: 1005,
src:
'https://lfdanlu.github.io/docPhotos/photos/DSC00888.jpeg'
},
{
id: 2,
name: 'Grassy Plain',
size: '233 KB',
width: 1024,
height: 683,
src:
'https://lfdanlu.github.io/docPhotos/photos/DSC01051.jpeg'
}
// ...
];
<CardView
items={items}
height="500px"
width="100%"
layout={GridLayout}
aria-label="Default selection CardView 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">JPEG</Text>
<Content>Size: {item.size}</Content>
</Card>
)}
</CardView>
let items = [
{
id: 1,
name: 'Icy Peak',
size: '206 KB',
width: 670,
height: 1005,
src:
'https://lfdanlu.github.io/docPhotos/photos/DSC00888.jpeg'
},
{
id: 2,
name: 'Grassy Plain',
size: '233 KB',
width: 1024,
height: 683,
src:
'https://lfdanlu.github.io/docPhotos/photos/DSC01051.jpeg'
}
// ...
];
<CardView
items={items}
height="500px"
width="100%"
layout={GridLayout}
aria-label="Default selection CardView 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">
JPEG</Text>
<Content>
Size:{' '}
{item.size}
</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 SelectionCardView(props) {
let items = [
// Same items as in the previous...
];
let [selectedKeys, setSelectedKeys] = React.useState(new Set([2]));
return (
<CardView
items={items}
height="500px"
width="100%"
layout={GridLayout}
aria-label="Controlled selection CardView 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">
JPEG
</Text>
<Content>Size: {item.size}</Content>
</Card>
)}
</CardView>
);
}
function SelectionCardView(props) {
let items = [
// Same items as in the previous...
];
let [selectedKeys, setSelectedKeys] = React.useState(
new Set([2])
);
return (
<CardView
items={items}
height="500px"
width="100%"
layout={GridLayout}
aria-label="Controlled selection CardView 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">JPEG</Text>
<Content>Size: {item.size}</Content>
</Card>
)}
</CardView>
);
}
function SelectionCardView(
props
) {
let items = [
// Same items as in the previous...
];
let [
selectedKeys,
setSelectedKeys
] = React.useState(
new Set([2])
);
return (
<CardView
items={items}
height="500px"
width="100%"
layout={GridLayout}
aria-label="Controlled selection CardView 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">
JPEG</Text>
<Content>
Size:{' '}
{item.size}
</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
<SelectionCardView selectionMode="single" />
// Using the same CardView as above
<SelectionCardView selectionMode="single" />
// Using the same CardView as above
<SelectionCardView 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
<SelectionCardView disallowEmptySelection />
// Using the same CardView as above
<SelectionCardView disallowEmptySelection />
// Using the same CardView as above
<SelectionCardView
disallowEmptySelection
/>
Disabled cards#
You can disable specific cards 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.
function Folder(props) {
// Custom Folder asset illustration implementation...
}
function File(props) {
// Custom File asset illustration implementation...
}
function CardViewDisabledKeys() {
let items = [
{
id: 1,
name: 'Vacation ideas',
type: 'file',
size: '100 KB',
format: 'DOCX'
},
{ id: 2, name: 'Saved Photos 2022', type: 'folder', size: '1.3 GB' }
// ...
];
return (
<CardView
items={items}
height="500px"
width="100%"
layout={GridLayout}
aria-label="Disabled selection CardView example"
selectionMode="multiple"
disabledKeys={[2, 4]}
>
{(item) => (
<Card textValue={item.name}>
{item.type === 'folder'
? <Folder alt="folder" slot="illustration" />
: <File alt="file" slot="illustration" />}
<Heading>{item.name}</Heading>
{item.format && <Text slot="detail">{item.format}</Text>}
<Content>Size: {item.size}</Content>
</Card>
)}
</CardView>
);
}
<CardViewDisabledKeys />
function Folder(props) {
// Custom Folder asset illustration implementation...
}
function File(props) {
// Custom File asset illustration implementation...
}
function CardViewDisabledKeys() {
let items = [
{
id: 1,
name: 'Vacation ideas',
type: 'file',
size: '100 KB',
format: 'DOCX'
},
{
id: 2,
name: 'Saved Photos 2022',
type: 'folder',
size: '1.3 GB'
}
// ...
];
return (
<CardView
items={items}
height="500px"
width="100%"
layout={GridLayout}
aria-label="Disabled selection CardView example"
selectionMode="multiple"
disabledKeys={[2, 4]}
>
{(item) => (
<Card textValue={item.name}>
{item.type === 'folder'
? <Folder alt="folder" slot="illustration" />
: <File alt="file" slot="illustration" />}
<Heading>{item.name}</Heading>
{item.format &&
<Text slot="detail">{item.format}</Text>}
<Content>Size: {item.size}</Content>
</Card>
)}
</CardView>
);
}
<CardViewDisabledKeys />
function Folder(props) {
// Custom Folder asset illustration implementation...
}
function File(props) {
// Custom File asset illustration implementation...
}
function CardViewDisabledKeys() {
let items = [
{
id: 1,
name:
'Vacation ideas',
type: 'file',
size: '100 KB',
format: 'DOCX'
},
{
id: 2,
name:
'Saved Photos 2022',
type: 'folder',
size: '1.3 GB'
}
// ...
];
return (
<CardView
items={items}
height="500px"
width="100%"
layout={GridLayout}
aria-label="Disabled selection CardView example"
selectionMode="multiple"
disabledKeys={[
2,
4
]}
>
{(item) => (
<Card
textValue={item
.name}
>
{item.type ===
'folder'
? (
<Folder
alt="folder"
slot="illustration"
/>
)
: (
<File
alt="file"
slot="illustration"
/>
)}
<Heading>
{item.name}
</Heading>
{item.format &&
(
<Text slot="detail">
{item
.format}
</Text>
)}
<Content>
Size:{' '}
{item.size}
</Content>
</Card>
)}
</CardView>
);
}
<CardViewDisabledKeys />
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 selection examples...
];
function CardViewActions() {
let [selectedKeys, setSelectedKeys] = React.useState(new Set([2]));
return (
<ActionBarContainer height="500px">
<CardView
items={items}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
height="500px"
width="100%"
layout={GridLayout}
aria-label="CardView with ActionBar example"
selectionMode="multiple"
>
{(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
isEmphasized
selectedItemCount={selectedKeys.size}
onClearSelection={() => {
setSelectedKeys(new Set());
}}
onAction={(key) =>
alert(`Performing action on cards.`)}
>
<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 selection examples...
];
function CardViewActions() {
let [selectedKeys, setSelectedKeys] = React.useState(
new Set([2])
);
return (
<ActionBarContainer height="500px">
<CardView
items={items}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
height="500px"
width="100%"
layout={GridLayout}
aria-label="CardView with ActionBar example"
selectionMode="multiple"
>
{(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
isEmphasized
selectedItemCount={selectedKeys.size}
onClearSelection={() => {
setSelectedKeys(new Set());
}}
onAction={(key) =>
alert(
`Performing
action on cards.`)}
>
<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 selection examples...
];
function CardViewActions() {
let [
selectedKeys,
setSelectedKeys
] = React.useState(
new Set([2])
);
return (
<ActionBarContainer height="500px">
<CardView
items={items}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
height="500px"
width="100%"
layout={GridLayout}
aria-label="CardView with ActionBar example"
selectionMode="multiple"
>
{(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
isEmphasized
selectedItemCount={selectedKeys
.size}
onClearSelection={() => {
setSelectedKeys(
new Set()
);
}}
onAction={(key) =>
alert(
`Performing
action on cards.`)}
>
<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#
Name | Type | Default | Description |
layout | CardViewLayoutConstructor<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. |
children | CollectionChildren<T> | — | The contents of the collection. |
cardOrientation | Orientation | — | The orientation of the cards within the CardView. |
isQuiet | boolean | — | Whether 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.Element | — | Sets what the CardView should render when there is no content to display. |
loadingState | LoadingState | — | The current loading state of the CardView. |
items | Iterable<T> | — | Item objects in the collection. |
disabledKeys | Iterable<Key> | — | The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with. |
selectionMode | SelectionMode | — | The type of selection that is allowed in the collection. |
disallowEmptySelection | boolean | — | Whether 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
Name | Type | Default | Description |
onSelectionChange | (
(keys: Selection
)) => any | — | Handler that is called when the selection changes. |
onLoadMore | () => any | — | Handler that is called when more items should be loaded, e.g. while scrolling near the bottom. |
Layout
Name | Type | Default | Description |
flex | Responsive<string
| number
| boolean> | — | When used in a flex layout, specifies how the element will grow or shrink to fit the space available. See MDN. |
flexGrow | Responsive<number> | — | When used in a flex layout, specifies how the element will grow to fit the space available. See MDN. |
flexShrink | Responsive<number> | — | When used in a flex layout, specifies how the element will shrink to fit the space available. See MDN. |
flexBasis | Responsive<number | string> | — | When used in a flex layout, specifies the initial main size of the element. See MDN. |
alignSelf | Responsive<'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. |
justifySelf | Responsive<'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. |
order | Responsive<number> | — | The layout order for the element within a flex or grid container. See MDN. |
gridArea | Responsive<string> | — | When used in a grid layout, specifies the named grid area that the element should be placed in within the grid. See MDN. |
gridColumn | Responsive<string> | — | When used in a grid layout, specifies the column the element should be placed in within the grid. See MDN. |
gridRow | Responsive<string> | — | When used in a grid layout, specifies the row the element should be placed in within the grid. See MDN. |
gridColumnStart | Responsive<string> | — | When used in a grid layout, specifies the starting column to span within the grid. See MDN. |
gridColumnEnd | Responsive<string> | — | When used in a grid layout, specifies the ending column to span within the grid. See MDN. |
gridRowStart | Responsive<string> | — | When used in a grid layout, specifies the starting row to span within the grid. See MDN. |
gridRowEnd | Responsive<string> | — | When used in a grid layout, specifies the ending row to span within the grid. See MDN. |
Spacing
Name | Type | Default | Description |
margin | Responsive<DimensionValue> | — | The margin for all four sides of the element. See MDN. |
marginTop | Responsive<DimensionValue> | — | The margin for the top side of the element. See MDN. |
marginBottom | Responsive<DimensionValue> | — | The margin for the bottom side of the element. See MDN. |
marginStart | Responsive<DimensionValue> | — | The margin for the logical start side of the element, depending on layout direction. See MDN. |
marginEnd | Responsive<DimensionValue> | — | The margin for the logical end side of an element, depending on layout direction. See MDN. |
marginX | Responsive<DimensionValue> | — | The margin for both the left and right sides of the element. See MDN. |
marginY | Responsive<DimensionValue> | — | The margin for both the top and bottom sides of the element. See MDN. |
Sizing
Name | Type | Default | Description |
width | Responsive<DimensionValue> | — | The width of the element. See MDN. |
minWidth | Responsive<DimensionValue> | — | The minimum width of the element. See MDN. |
maxWidth | Responsive<DimensionValue> | — | The maximum width of the element. See MDN. |
height | Responsive<DimensionValue> | — | The height of the element. See MDN. |
minHeight | Responsive<DimensionValue> | — | The minimum height of the element. See MDN. |
maxHeight | Responsive<DimensionValue> | — | The maximum height of the element. See MDN. |
Positioning
Name | Type | Default | Description |
position | Responsive<'static'
| 'relative'
| 'absolute'
| 'fixed'
| 'sticky'> | — | Specifies how the element is positioned. See MDN. |
top | Responsive<DimensionValue> | — | The top position for the element. See MDN. |
bottom | Responsive<DimensionValue> | — | The bottom position for the element. See MDN. |
left | Responsive<DimensionValue> | — | The left position for the element. See MDN. Consider using start instead for RTL support. |
right | Responsive<DimensionValue> | — | The right position for the element. See MDN. Consider using start instead for RTL support. |
start | Responsive<DimensionValue> | — | The logical start position for the element, depending on layout direction. See MDN. |
end | Responsive<DimensionValue> | — | The logical end position for the element, depending on layout direction. See MDN. |
zIndex | Responsive<number> | — | The stacking order for the element. See MDN. |
isHidden | Responsive<boolean> | — | Hides the element. |
Accessibility
Name | Type | Default | Description |
id | string | — | The element's unique identifier. See MDN. |
aria-label | string | — | Defines a string value that labels the current element. |
aria-labelledby | string | — | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | — | Identifies the element (or elements) that describes the object. |
aria-details | string | — | Identifies the element (or elements) that provide a detailed, extended description for the object. |
Advanced
Name | Type | Default | Description |
UNSAFE_className | string | — | Sets the CSS className for the element. Only use as a last resort. Use style props instead. |
UNSAFE_style | CSSProperties | — | Sets inline style for the element. Only use as a last resort. Use style props instead. |
Card props#
Name | Type | Default | Description |
children | ReactNode | — | Rendered contents of the card. Note that focusable elements are not allowed within the card in a CardView. |
textValue | string | — | A string representation of the cards's contents, used for features like typeahead. |
key | Key | — | A unique key for the card. |
Sizing
Name | Type | Default | Description |
width | number | — | The raw width of the card's image. |
height | number | — | The raw height of the card's image. |
Accessibility
Name | Type | Default | Description |
aria-label | string | — | An 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.
// Same CardView from controlled selection example...
<SelectionCardView
layout={GridLayout}
aria-label="CardView with horizontal cards"
height="300px"
cardOrientation="horizontal"
/>
// Same CardView from controlled selection example...
<SelectionCardView
layout={GridLayout}
aria-label="CardView with horizontal cards"
height="300px"
cardOrientation="horizontal"
/>
// Same CardView from controlled selection example...
<SelectionCardView
layout={GridLayout}
aria-label="CardView with horizontal cards"
height="300px"
cardOrientation="horizontal"
/>
Quiet#
WaterfallLayout is the only layout that currently supports quiet cards. To enable quiet cards, provide isQuiet
to the CardView.
// Same CardView from controlled selection example...
<SelectionCardView
layout={WaterfallLayout}
aria-label="CardView with quiet cards"
isQuiet
/>
// Same CardView from controlled selection example...
<SelectionCardView
layout={WaterfallLayout}
aria-label="CardView with quiet cards"
isQuiet
/>
// Same CardView from controlled selection example...
<SelectionCardView
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>