---
id: Card view
section: patterns
---

import DashboardWrapper from '@patternfly/react-core/src/demos/examples/DashboardWrapper';

import TrashIcon from '@patternfly/react-icons/dist/esm/icons/trash-icon';
import PlusCircleIcon from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
import pfIcon from '../assets/pf-logo-small.svg';
import activeMQIcon from '../assets/activemq-core_200x150.png';
import avroIcon from '../assets/camel-avro_200x150.png';
import dropBoxIcon from '../assets/camel-dropbox_200x150.png';
import infinispanIcon from '../assets/camel-infinispan_200x150.png';
import saxonIcon from '../assets/camel-saxon_200x150.png';
import sparkIcon from '../assets/camel-spark_200x150.png';
import swaggerIcon from '../assets/camel-swagger-java_200x150.png';
import azureIcon from '../assets/FuseConnector_Icons_AzureServices.png';
import restIcon from '../assets/FuseConnector_Icons_REST.png';
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
import { data } from './CardData.jsx';

## Demos

This demonstrates how you can assemble a full page view that contains a grid of equal sized cards that includes a toolbar for managing card grid contents.

### Card view

```js isFullscreen
import React from 'react';
import {
  Badge,
  Bullseye,
  Button,
  Card,
  CardHeader,
  CardTitle,
  CardBody,
  Divider,
  Dropdown,
  DropdownItem,
  DropdownList,
  EmptyState,
  EmptyStateHeader,
  EmptyStateIcon,
  EmptyStateFooter,
  EmptyStateVariant,
  EmptyStateActions,
  Gallery,
  MenuToggle,
  MenuToggleCheckbox,
  OverflowMenu,
  OverflowMenuControl,
  OverflowMenuDropdownItem,
  OverflowMenuItem,
  PageSection,
  PageSectionVariants,
  Pagination,
  TextContent,
  Text,
  Toolbar,
  ToolbarItem,
  ToolbarFilter,
  ToolbarContent,
  Select,
  SelectList,
  SelectOption
} from '@patternfly/react-core';
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
import DashboardWrapper from '@patternfly/react-core/src/demos/examples/DashboardWrapper';
import TrashIcon from '@patternfly/react-icons/dist/esm/icons/trash-icon';
import PlusCircleIcon from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
import pfIcon from '../assets/pf-logo-small.svg';
import activeMQIcon from '../assets/activemq-core_200x150.png';
import avroIcon from '../assets/camel-avro_200x150.png';
import dropBoxIcon from '../assets/camel-dropbox_200x150.png';
import infinispanIcon from '../assets/camel-infinispan_200x150.png';
import saxonIcon from '../assets/camel-saxon_200x150.png';
import sparkIcon from '../assets/camel-spark_200x150.png';
import swaggerIcon from '../assets/camel-swagger-java_200x150.png';
import azureIcon from '../assets/FuseConnector_Icons_AzureServices.png';
import restIcon from '../assets/FuseConnector_Icons_REST.png';
import { data } from './CardData.jsx';

class CardViewBasic extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      filters: {
        products: []
      },
      cardData: data,
      isChecked: false,
      selectedItems: [],
      areAllSelected: false,
      isUpperToolbarDropdownOpen: false,
      isUpperToolbarKebabDropdownOpen: false,
      isLowerToolbarDropdownOpen: false,
      isLowerToolbarKebabDropdownOpen: false,
      isCardKebabDropdownOpen: false,
      activeItem: 0,
      splitButtonDropdownIsOpen: false,
      page: 1,
      perPage: 10,
      totalItemCount: 10
    };

    this.checkAllSelected = (selected, total) => {
      if (selected && selected < total) {
        return null;
      }
      return selected === total;
    };

    this.onToolbarDropdownToggle = () => {
      this.setState((prevState) => ({
        isLowerToolbarDropdownOpen: !prevState.isLowerToolbarDropdownOpen
      }));
    };

    this.onToolbarKebabDropdownToggle = () => {
      this.setState({
        isOpen: !this.state.isLowerToolbarKebabDropdownOpen
      });
    };

    this.onToolbarKebabDropdownSelect = (event) => {
      this.setState({
        isLowerToolbarKebabDropdownOpen: !this.state.isLowerToolbarKebabDropdownOpen
      });
    };

    this.onCardKebabDropdownToggle = (key, event) => {
      this.setState((prevState) => ({
        [key]: !prevState[key]
      }));
    };

    this.onCardKebabDropdownSelect = (key) => {
      this.setState({
        [key]: !this.state[key]
      });
    };

    this.deleteItem = (item) => () => {
      const filter = (getter) => (val) => getter(val) !== item.id;
      this.setState({
        cardData: this.state.cardData.filter(filter(({ id }) => id)),
        selectedItems: this.state.selectedItems.filter(filter((id) => id))
      });
    };

    this.onSetPage = (_event, pageNumber) => {
      this.setState({
        page: pageNumber
      });
    };

    this.onPerPageSelect = (_event, perPage) => {
      this.setState({
        perPage,
        page: 1
      });
    };

    this.onSplitButtonToggle = () => {
      this.setState((prevState) => ({
        splitButtonDropdownIsOpen: !prevState.splitButtonDropdownIsOpen
      }));
    };

    this.onSplitButtonSelect = () => {
      this.setState({
        splitButtonDropdownIsOpen: false
      });
    };

    this.onNameSelect = (event, selection) => {
      const checked = event.target.checked;
      this.setState((prevState) => {
        const prevSelections = prevState.filters.products;
        return {
          filters: {
            ...prevState.filters,
            ['products']: checked
              ? [...prevSelections, selection]
              : prevSelections.filter((value) => value !== selection)
          }
        };
      });
    };

    this.onDelete = (type = '', id = '') => {
      if (type) {
        this.setState((prevState) => {
          prevState.filters[type.toLowerCase()] = prevState.filters[type.toLowerCase()].filter((s) => s !== id);
          return {
            filters: prevState.filters
          };
        });
      } else {
        this.setState({
          filters: {
            products: []
          }
        });
      }
    };

    this.onClick = (productId) => {
      this.setState((prevState) =>
        prevState.selectedItems.includes(productId * 1)
          ? {
              selectedItems: [...prevState.selectedItems.filter((id) => productId * 1 != id)],
              areAllSelected: this.checkAllSelected(prevState.selectedItems.length - 1, prevState.totalItemCount)
            }
          : {
              selectedItems: [...prevState.selectedItems, productId * 1],
              areAllSelected: this.checkAllSelected(prevState.selectedItems.length + 1, prevState.totalItemCount)
            }
      );
    };
  }

  selectedItems(e) {
    const { value, checked } = e.target;
    let { selectedItems } = this.state;

    if (checked) {
      selectedItems = [...selectedItems, value];
    } else {
      selectedItems = selectedItems.filter((el) => el !== value);
      if (this.state.areAllSelected) {
        this.setState({
          areAllSelected: !this.state.areAllSelected
        });
      }
    }
    this.setState({ selectedItems });
  }

  splitCheckboxSelectAll(e) {
    const { checked } = e.target;
    const { isChecked } = this.state;
    let collection = [];

    if (checked) {
      for (let i = 0; i <= 9; i++) {
        collection = [...collection, i];
      }
    }

    this.setState(
      {
        selectedItems: collection,
        isChecked,
        areAllSelected: checked
      },
      this.updateSelected
    );
  }

  selectPage(e) {
    const { checked } = e.target;
    const { totalItemCount, perPage } = this.state;
    let collection = [];

    collection = this.getAllItems();

    this.setState(
      {
        selectedItems: collection,
        isChecked: checked,
        areAllSelected: totalItemCount === perPage ? true : false
      },
      this.updateSelected
    );
  }

  selectAll() {

    let collection = [];
    for (let i = 0; i <= 9; i++) {
      collection = [...collection, i];
    }

    this.setState(
      {
        selectedItems: collection,
        isChecked: true,
        areAllSelected: true
      },
      this.updateSelected
    );
  }

  selectNone() {
    this.setState(
      {
        selectedItems: [],
        isChecked: false,
        areAllSelected: false
      },
      this.updateSelected
    );
  }

  getAllItems() {
    const { cardData } = this.state;
    const collection = [];
    for (const items of cardData) {
      collection.push(items.id);
    }

    return collection;
  }

  updateSelected() {
    const { cardData, selectedItems } = this.state;
    const rows = cardData.map((post) => {
      post.selected = selectedItems.includes(post.id);
      return post;
    });

    this.setState({
      cardData: rows
    });
  }

  renderPagination() {
    const { page, perPage, totalItemCount } = this.state;

    const defaultPerPageOptions = [
      {
        title: '1',
        value: 1
      },
      {
        title: '5',
        value: 5
      },
      {
        title: '10',
        value: 10
      }
    ];

    return (
      <Pagination
        itemCount={totalItemCount}
        page={page}
        perPage={perPage}
        perPageOptions={defaultPerPageOptions}
        onSetPage={this.onSetPage}
        onPerPageSelect={this.onPerPageSelect}
        variant="top"
        isCompact
      />
    );
  }

  buildSelectDropdown() {
    const { splitButtonDropdownIsOpen, selectedItems, areAllSelected } = this.state;
    const numSelected = selectedItems.length;
    const allSelected = areAllSelected;
    const anySelected = numSelected > 0;
    const someChecked = anySelected ? null : false;
    const isChecked = allSelected ? true : someChecked;
    const splitButtonDropdownItems = (
      <>
        <DropdownItem key="item-1" onClick={this.selectNone.bind(this)}>
          Select none (0 items)
        </DropdownItem>
        <DropdownItem key="item-2" onClick={this.selectPage.bind(this)}>
          Select page ({this.state.perPage} items)
        </DropdownItem>
        <DropdownItem key="item-3" onClick={this.selectAll.bind(this)}>
          Select all ({this.state.totalItemCount} items)
        </DropdownItem>
      </>
    );
    return (
      <Dropdown
        onSelect={this.onSplitButtonSelect}
        isOpen={splitButtonDropdownIsOpen}
        onOpenChange={(isOpen) => this.setState({ splitButtonDropdownIsOpen: isOpen })}
        toggle={(toggleRef) => (
          <MenuToggle
            ref={toggleRef}
            isExpanded={splitButtonDropdownIsOpen}
            onClick={this.onSplitButtonToggle}
            aria-label="Select cards"
            splitButtonOptions={{
              items: [
                <MenuToggleCheckbox
                  id="split-dropdown-checkbox"
                  key="split-dropdown-checkbox"
                  aria-label={anySelected ? 'Deselect all cards' : 'Select all cards'}
                  isChecked={areAllSelected}
                  onClick={this.splitCheckboxSelectAll.bind(this)}
                >
                  {numSelected !== 0 && `${numSelected} selected`}
                </MenuToggleCheckbox>
              ]
            }}
          ></MenuToggle>
        )}
      >
        <DropdownList>{splitButtonDropdownItems}</DropdownList>
      </Dropdown>
    );
  }

  buildFilterDropdown() {
    const { isLowerToolbarDropdownOpen, filters } = this.state;

    const filterDropdownItems = (
      <SelectList>
        <SelectOption
          hasCheckbox
          key="patternfly"
          value="PatternFly"
          isSelected={filters.products.includes('PatternFly')}
        >
          PatternFly
        </SelectOption>
        <SelectOption hasCheckbox key="activemq" value="ActiveMQ" isSelected={filters.products.includes('ActiveMQ')}>
          ActiveMQ
        </SelectOption>
        <SelectOption
          hasCheckbox
          key="apachespark"
          value="Apache Spark"
          isSelected={filters.products.includes('Apache Spark')}
        >
          Apache Spark
        </SelectOption>
        <SelectOption hasCheckbox key="avro" value="Avro" isSelected={filters.products.includes('Avro')}>
          Avro
        </SelectOption>
        <SelectOption
          hasCheckbox
          key="azureservices"
          value="Azure Services"
          isSelected={filters.products.includes('Azure Services')}
        >
          Azure Services
        </SelectOption>
        <SelectOption hasCheckbox key="crypto" value="Crypto" isSelected={filters.products.includes('Crypto')}>
          Crypto
        </SelectOption>
        <SelectOption hasCheckbox key="dropbox" value="DropBox" isSelected={filters.products.includes('DropBox')}>
          DropBox
        </SelectOption>
        <SelectOption
          hasCheckbox
          key="jbossdatagrid"
          value="JBoss Data Grid"
          isSelected={filters.products.includes('JBoss Data Grid')}
        >
          JBoss Data Grid
        </SelectOption>
        <SelectOption hasCheckbox key="rest" value="REST" isSelected={filters.products.includes('REST')}>
          REST
        </SelectOption>
        <SelectOption hasCheckbox key="swagger" value="SWAGGER" isSelected={filters.products.includes('SWAGGER')}>
          SWAGGER
        </SelectOption>
      </SelectList>
    );

    return (
      <ToolbarFilter categoryName="Products" chips={filters.products} deleteChip={this.onDelete}>
        <Select
          aria-label="Products"
          role="menu"
          toggle={(toggleRef) => (
            <MenuToggle ref={toggleRef} onClick={this.onToolbarDropdownToggle} isExpanded={isLowerToolbarDropdownOpen}>
              Filter by creator name
              {filters.products.length > 0 && <Badge isRead>{filters.products.length}</Badge>}
            </MenuToggle>
          )}
          onSelect={this.onNameSelect}
          onOpenChange={(isOpen) => {
            this.setState(() => ({
              isLowerToolbarDropdownOpen: isOpen
            }));
          }}
          selected={filters.products}
          isOpen={isLowerToolbarDropdownOpen}
        >
          {filterDropdownItems}
        </Select>
      </ToolbarFilter>
    );
  }

  render() {
    const {
      isLowerToolbarKebabDropdownOpen,
      filters,
      cardData,
      selectedItems,
      totalItemCount,
      onPerPageSelect,
      onSetPage,
      page,
      perPage
    } = this.state;

    const toolbarKebabDropdownItems = [
      <OverflowMenuDropdownItem itemId={0} key="link">
        Link
      </OverflowMenuDropdownItem>,
      <OverflowMenuDropdownItem itemId={1} key="action" component="button">
        Action
      </OverflowMenuDropdownItem>,
      <OverflowMenuDropdownItem itemId={2} key="disabled link" isDisabled>
        Disabled Link
      </OverflowMenuDropdownItem>,
      <OverflowMenuDropdownItem itemId={3} key="disabled action" isDisabled component="button">
        Disabled Action
      </OverflowMenuDropdownItem>,
      <Divider key="separator" />,
      <OverflowMenuDropdownItem itemId={5} key="separated link">
        Separated Link
      </OverflowMenuDropdownItem>,
      <OverflowMenuDropdownItem itemId={6} key="separated action" component="button">
        Separated Action
      </OverflowMenuDropdownItem>
    ];

    const toolbarItems = (
      <React.Fragment>
        <ToolbarItem variant="bulk-select">{this.buildSelectDropdown()}</ToolbarItem>
        <ToolbarItem breakpoint="xl">{this.buildFilterDropdown()}</ToolbarItem>
        <ToolbarItem variant="overflow-menu">
          <OverflowMenu breakpoint="md">
            <OverflowMenuItem>
              <Button variant="primary">Create a project</Button>
            </OverflowMenuItem>
            <OverflowMenuControl hasAdditionalOptions>
              <Dropdown
                onSelect={this.onToolbarKebabDropdownSelect}
                toggle={(toggleRef) => (
                  <MenuToggle
                    ref={toggleRef}
                    aria-label="Toolbar kebab overflow menu"
                    variant="plain"
                    onClick={this.onToolbarKebabDropdownToggle}
                    isExpanded={isLowerToolbarKebabDropdownOpen}
                  >
                    <EllipsisVIcon />
                  </MenuToggle>
                )}
                isOpen={isLowerToolbarKebabDropdownOpen}
                onOpenChange={(isOpen) => this.setState({ isLowerToolbarKebabDropdownOpen: isOpen })}
              >
                <DropdownList>{toolbarKebabDropdownItems}</DropdownList>
              </Dropdown>
            </OverflowMenuControl>
          </OverflowMenu>
        </ToolbarItem>
        <ToolbarItem variant="pagination" align={{ default: 'alignRight' }}>
          {this.renderPagination()}
        </ToolbarItem>
      </React.Fragment>
    );

    const icons = {
      pfIcon,
      activeMQIcon,
      sparkIcon,
      avroIcon,
      azureIcon,
      saxonIcon,
      dropBoxIcon,
      infinispanIcon,
      restIcon,
      swaggerIcon
    };

    const filtered =
      filters.products.length > 0
        ? data.filter((card) => filters.products.length === 0 || filters.products.includes(card.name))
        : cardData.slice((page - 1) * perPage, perPage === 1 ? page * perPage : page * perPage - 1);

    return (
      <React.Fragment>
        <DashboardWrapper mainContainerId="main-content-card-view-default-nav" breadcrumb={null}>
          <PageSection variant={PageSectionVariants.light}>
            <TextContent>
              <Text component="h1">Projects</Text>
              <Text component="p">This is a demo that showcases PatternFly cards.</Text>
            </TextContent>
            <Toolbar id="toolbar-group-types" clearAllFilters={this.onDelete}>
              <ToolbarContent>{toolbarItems}</ToolbarContent>
            </Toolbar>
          </PageSection>
          <PageSection isFilled>
            <Gallery hasGutter aria-label="Selectable card container">
              <Card isCompact>
                <Bullseye>
                  <EmptyState variant={EmptyStateVariant.xs}>
                    <EmptyStateHeader
                      headingLevel="h2"
                      titleText="Add a new card to your page"
                      icon={<EmptyStateIcon icon={PlusCircleIcon} />}
                    />
                    <EmptyStateFooter>
                      <EmptyStateActions>
                        <Button variant="link">Add card</Button>
                      </EmptyStateActions>
                    </EmptyStateFooter>
                  </EmptyState>
                </Bullseye>
              </Card>
              {filtered.map((product, key) => (
                <Card
                  isCompact
                  isClickable
                  isSelectable
                  key={product.name}
                  id={product.name.replace(/ /g, '-')}
                >
                  <CardHeader
                    selectableActions={{
                      isChecked: selectedItems.includes(product.id),
                      selectableActionId: `selectable-actions-item-${product.id}`,
                      selectableActionAriaLabelledby: product.name.replace(/ /g, '-'),
                      name: `check-${product.id}`,
                      onChange: () => this.onClick(product.id)
                    }}
                    actions={{
                      actions: (
                        <>
                          <Dropdown
                            isOpen={this.state[key] ?? false}
                            onOpenChange={(isOpen) => this.setState({ [key]: isOpen })}
                            toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
                              <MenuToggle
                                ref={toggleRef}
                                aria-label={`${product.name} actions`}
                                variant="plain"
                                onClick={(e) => {
                                  this.onCardKebabDropdownToggle(key, e);
                                }}
                                isExpanded={this.state[key]}
                              >
                                <EllipsisVIcon />
                              </MenuToggle>
                            )}
                            popperProps={{ position: 'right' }}
                          >
                            <DropdownList>
                              <DropdownItem key="trash" onClick={this.deleteItem(product)}>
                                <TrashIcon />
                                Delete
                              </DropdownItem>
                            </DropdownList>
                          </Dropdown>
                        </>
                      )
                    }}
                  >
                    <img src={icons[product.icon]} alt={`${product.name} icon`} style={{ maxWidth: '60px' }} />
                  </CardHeader>
                  <CardTitle>{product.name}</CardTitle>
                  <CardBody>{product.description}</CardBody>
                </Card>
              ))}
            </Gallery>
          </PageSection>
          <PageSection isFilled={false} sticky="bottom" padding={{ default: 'noPadding' }} variant="light">
            <Pagination
              itemCount={totalItemCount}
              page={page}
              perPage={perPage}
              onPerPageSelect={onPerPageSelect}
              onSetPage={onSetPage}
              variant="bottom"
            />
          </PageSection>
        </DashboardWrapper>
      </React.Fragment>
    );
  }
}
```
