Accordion

Try it out

Best practices

  • If you need to group accordions together, make sure to place them inside the Accordion.Provider component, as it provides the keyboard accessibility features for groups of accordions.

Basic usage

You can use a single Accordion by itself:

import React from 'react'
import { Accordion, Text } from '@repay/cactus-web'

const SingleAccordion = props => {
  return (
    <Accordion>
      <Accordion.Header>
        <Text as="h3">My Header</Text>
      </Accordion.Header>
      <Accordion.Body>Accordion Content Goes Here</Accordion.Body>
    </Accordion>
  )
}

Accordion Provider

You can also use Accordion.Provider to display multiple Accordions at the same time. The provider will manage the focus for you in an accessible way and allows for both controlled and uncontrolled management of open/closed state.

To use a controlled provider, be sure to give each of your Accordions an id prop. You can then pass in an array of IDs to the openId prop of the provider. You’ll also want to pass an onChange handler to respond to the toggling of Accordions.

If you’d rather use an uncontrolled provider, you won’t need to pass in an openId prop, as the provider will manage the open/closed state for you. You can set the maximum number of Accordions that can be open at the same time using the maxOpen prop, which defaults to 1. You can also pass defaultOpen={true} to any Accordion that you’d like to initialize in an open state.

Controlled Example:

const ControlledAccordions = () => {
  const [open, setOpen] = useState<string[]>(['accordion2'])
  const toggleAccordion = (toggleId: string) => {
    setOpen((currentOpen) => {
      if (currentOpen.includes(toggleId)) {
        return currentOpen.filter((id) => id !== toggleId)
      }
      return [...currentOpen, toggleId]
    })
  }

  return (
    <Accordion.Provider openId={open} onChange={toggleAccordion}>
      <Accordion id="accordion1">
        <Accordion.Header>
          <Text as="h3">My Header 1</Text>
        </Accordion.Header>
        <Accordion.Body>Accordion Content Goes Here</Accordion.Body>
      </Accordion>
      <Accordion id="accordion2">
        <Accordion.Header>
          <Text as="h3">My Header 2</Text>
        </Accordion.Header>
        <Accordion.Body>I default to open because "openId" was initialized with my ID.</Accordion.Body>
      </Accordion>
      <Accordion id="accordion3">
        <Accordion.Header>
          <Text as="h3">My Header 3</Text>
        </Accordion.Header>
        <Accordion.Body>Accordion Content Goes Here</Accordion.Body>
      </Accordion>
    </Accordion.Provider>
  )
}

Uncontrolled Example:

import React from 'react'
import { Accordion, Text } from '@repay/cactus-web'

const MultipleAccordions = props => {
  return (
    <Accordion.Provider maxOpen={2}>
      <Accordion>
        <Accordion.Header>
          <Text as="h3">My Header 1</Text>
        </Accordion.Header>
        <Accordion.Body>Accordion Content Goes Here</Accordion.Body>
      </Accordion>
      <Accordion defaultOpen={true}>
        <Accordion.Header>
          <Text as="h3">My Header 2</Text>
        </Accordion.Header>
        <Accordion.Body>I default to open because "defaultOpen" was set to true.</Accordion.Body>
      </Accordion>
      <Accordion>
        <Accordion.Header>
          <Text as="h3">My Header 3</Text>
        </Accordion.Header>
        <Accordion.Body>Accordion Content Goes Here</Accordion.Body>
      </Accordion>
    </Accordion.Provider>
  )
}

Outline Variant

We have included an outline variant which can be used to better depict hierarchical structures as well as separations in the page.

Usage:

import React from 'react'
import { Accordion, Text } from '@repay/cactus-web'

const OutlineAccordion = props => {
  return (
    <Accordion variant="outline">
      <Accordion.Header>
        <Text as="h3">Example Header</Text>
      </Accordion.Header>
      <Accordion.Body>Example Content</Accordion.Body>
    </Accordion>
  )
}

Render Prop

To allow further customization of the accordions, we’ve provided the option to pass a render function to the header. This function receives an object which includes a variable indicating whether or not the accordion is open, as well as an id that can be placed on whichever element describes the accordion.

Usage:

import React, { useState } from 'react'
import { Accordion, IconButton, Text } from '@repay/cactus-web'
import { ActionsDelete, NavigationCircleDown, NavigationCircleUp } from '@repay/cactus-icons'

const initialAccordions = [
  {
    header: 'Header 1',
    body: 'Body 1'
  },
  {
    header: 'Header 2'
    body: 'Body 2'
  }
]

const CustomAccordions = props => {
  const [accordions, setAccordions] = useState(initialAccordions)

  const handleUpClick = index => {
    accordionsCopy = [...accordions]
    const temp = accordionsCopy[index - 1]
    accordionsCopy[index - 1] = accordionsCopy[index]
    accordionsCopy[index] = temp
    setAccordions(accordionsCopy)
  }

  const handleDownClick = index => {
    accordionsCopy = [...accordions]
    const temp = accordionsCopy[index + 1]
    accordionsCopy[index + 1] = accordionsCopy[index]
    accordionsCopy[index] = temp
    setAccordions(accordionsCopy)
  }

  const handleDelete = index => {
    const accordionsCopy = [...accordions]
    accordionsCopy.splice(index, 1)
    setAccordions(accordionsCopy)
  }

  return (
    <Accordion.Provider>
      {accordions.map((acc, index) => (
        <Accordion variant="outline" key={acc.header}> render={({ isOpen, headerId }) => (
          <Flex alignItems="center" width="100%>
            <Text as="h3" id={headerId}>{acc.header}</Text>
            {isOpen && (
              <IconButton
                iconSize="medium"
                variant="danger"
                ml="auto"
                mr={4}
                label={`Delete ${acc.header}`}
                onClick={e => {
                  handleDelete(index)
                  e.stopPropagation()
                }}
              >
                <ActionsDelete aria-hidden="true" />
              </IconButton>
            )}
            <Flex
              alignItems="center"
              ml={isOpen ? 0 : 'auto'}
              pl={4}
              borderLeft="1px solid"
              borderLeftColor="lightContrast"
            >
              <IconButton
                iconSize="medium"
                mr={1}
                label={`Move ${acc.header} down`}
                disabled={index === accordions.length - 1}
                onClick={e => {
                  handleDownClick(index)
                  e.stopPropagation()
                }}
              >
                <NavigationCircleDown aria-hidden="true" />
              </IconButton>
              <IconButton
                iconSize="medium"
                label={`Move ${acc.header} up`}
                disabled={index === accordions.length - 1}
                onClick={e => {
                  handleUpClick(index)
                  e.stopPropagation()
                }}
              >
                <NavigationCircleUp aria-hidden="true" />
              </IconButton>
            </Flex>
          </Flex>
        )} />
      ))}
    </Accordion.Provider>
  )
}

Properties

Accordion

Cactus Props

NameRequiredDefault ValueTypeDescription
defaultOpenNboolean | undefinedDoes not apply when Accordion descends from a controlled Provider. If true, the Accordion will begin in the open state when first rendered.
useBoxShadowsNboolean | undefined
variantNsimpleAccordionVariants | undefined

Styling Props

NameRequiredTypeDescription
flexNResponsiveValue<Flex<TLengthStyledSystem>, Required<Theme<TLengthStyledSystem>>> | undefinedThe flex CSS property specifies how a flex item will grow or shrink so as to fit the space available in its flex container. This is a shorthand property that sets flex-grow, flex-shrink, and flex-basis. [MDN reference](https://developer.mozilla.org/en-US/docs/Web/CSS/flex)
flexBasisNResponsiveValue<FlexBasis<TLengthStyledSystem>, Required<Theme<TLengthStyledSystem>>> | undefined
flexGrowNResponsiveValue<FlexGrow, Required<Theme<TLengthStyledSystem>>> | undefinedThe flex-grow CSS property sets the flex grow factor of a flex item main size. It specifies how much of the remaining space in the flex container should be assigned to the item (the flex grow factor). [MDN reference](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow)
flexShrinkNResponsiveValue<FlexShrink, Required<Theme<TLengthStyledSystem>>> | undefinedThe flex-shrink CSS property sets the flex shrink factor of a flex item. If the size of all flex items is larger than the flex container, items shrink to fit according to flex-shrink. [MDN reference](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink)
mNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top, left, bottom and right
marginNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top, left, bottom and right
marginBottomNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on bottom
marginLeftNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on left
marginRightNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on right
marginTopNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top
marginXNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on left and right
marginYNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top and bottom
maxWidthNResponsiveValue<MaxWidth<TLengthStyledSystem>, Required<Theme<TLengthStyledSystem>>> | undefinedThe max-width CSS property sets the maximum width of an element. It prevents the used value of the width property from becoming larger than the value specified by max-width. [MDN reference](https://developer.mozilla.org/en-US/docs/Web/CSS/max-width)
mbNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on bottom
mlNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on left
mrNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on right
mtNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top
mxNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on left and right
myNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top and bottom
widthNResponsiveValue<Width<TLengthStyledSystem>, Required<Theme<TLengthStyledSystem>>> | undefinedThe width utility parses a component's `width` prop and converts it into a CSS width declaration. - Numbers from 0-1 are converted to percentage widths. - Numbers greater than 1 are converted to pixel values. - String values are passed as raw CSS values. - And arrays are converted to responsive width styles.

Accordion Header

Cactus Props

NameRequiredDefault ValueTypeDescription
renderN((opts: { isOpen: boolean; headerId: string; }) => Element) | undefined

Accordion Body

Cactus Props

There are no custom props.

Styling Props

NameRequiredTypeDescription
mNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top, left, bottom and right
marginNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top, left, bottom and right
marginBottomNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on bottom
marginLeftNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on left
marginRightNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on right
marginTopNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top
marginXNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on left and right
marginYNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top and bottom
mbNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on bottom
mlNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on left
mrNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on right
mtNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top
mxNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on left and right
myNResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefinedMargin on top and bottom

Accordion Provider

Cactus Props

NameRequiredDefault ValueTypeDescription
maxOpenNnumber | undefinedOnly used in uncontrolled variant. Determines maximum number of Accordions that can be open at one time.
onChangeN((changedId: string) => void) | undefinedCalled whenever an Accordion is toggled from open to closed or vice versa. The ID of the toggled Accordion is passed to the function.
openIdNstring | string[] | undefinedOnly used in controlled variant. Contains the IDs of all Accordions that should be in an open state.