Card
Cards collect and divide content into designated areas. Cards can be actionable themselves or have actionable elements within.
Loading...
Overview
Resources
Install
yarn add @camp/cardProps
| Prop | Type | Default | Description |
|---|---|---|---|
style † | CSSProperties | — | |
key † | Key | — | |
actionable | boolean | — | Setting actionable to true enables on hover effect to indicate clickability to user. |
ai | boolean | — | Set to true for AI appearance |
onClick † | MouseEventHandler<HTMLDivElement> | — | |
onKeyDown † | KeyboardEventHandler<HTMLDivElement> | — | |
role † | AriaRole | — | |
tabIndex † | number | — | |
disabled | boolean | — | Render card in `disabled` state when `actionable` is true. Recommend using `CardContent` for content to automatically apply disabled styles in content. |
dismissLabel | string | — | Translated aria label for the dismiss "X" button. Required if `onDismiss` is provided. |
dismissTestId | string | — | Test ID for the dismiss button. |
loading | boolean | — | When `ai` is true, set to true to show the card in a loading state |
loadingBackgroundColor | "bg-default" | "bg-educational" | "bg-disabled" | bg-default | When `loading` and `ai` are true, this prop sets the background color of the loading card. |
loadingContent | ReactNode | — | When `loading` and `ai` is true, this custom content will be rendered within the card while it is in a loading state. |
onDismiss | (() => void) & (() => void) & (() => void) & (() => void) & (() => void) | — | When actionable is false, onDismiss can be used to provide a dismiss button for the card. If provided, a close "X" button will be rendered on the top right and clicking close will fire onDismiss callback. (Actionable cards cannot be dismissed - `onDismiss` will be ignored if `actionable` is true.) |
renderMenu | () => ReactNode | — | |
selected | boolean | — | When actionable is true, shows a selected state for the card. |
Generated from @camp/card@7.16.0 source, ordered by production usage. * required. † standard HTML attribute — all native attributes are supported; only those with production usage are listed.
Upgrading to Next Gen
🎨 Updated Color Palette & Style Refresh: Aligned styling with our refreshed brand standards, delivering a modernized and cohesive look and feel.
Migration steps
- Update import path: Replace the old Card imports with Card from
@camp/card. - Update prop values:
disabled: used to disable anactionablecard and prevent any interactions with it. Note: you will need to manually set any buttons to be disabled within it.dismissLabel: used to pass a translated string to the dismiss icononDismiss: whenactionableis false, this can be used to pass a function when the dismiss button is clicked.selected:actionablecards can be shown with aselectedstate
- New
<CardContent>component: The<CardContent>is a preformatted component that handles the layout of the card content. It accepts props fortitle,paragraph, and render functions for various types of content. Card may still be used as a container for other custom styled children, but the<CardContent>component is recommended for most use cases. For full prop details, see the CardContent tab in the Overview section above.
Variations
Default card
Use the default card in most cases when looking to group related content together. Card is the outer container that defines outer styling including padding/border, and CardContent is the inner container which handles the layout of the inner content.
Loading...
import { Card, CardContent } from '@camp/card';
import { Chip } from '@camp/chip';
import { Button } from '@camp/button';
<Card>
<CardContent
title="Title"
paragraph="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labe et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
renderChips={() => (
<div style={{ display: 'flex', gap: Space[2] }}>
<Chip type="default" color="ac-blue">
Chip
</Chip>
<Chip type="default" color="moss">
Chip
</Chip>
</div>
)}
renderActions={() => (
<div style={{ display: 'flex', gap: Space[3] }}>
<Button size="small" appearance="secondary">
Card action
</Button>
<Button size="small" appearance="secondary">
Card action
</Button>
</div>
)}
/>
</Card>;Card with Image
<CardContent> also accepts a render function for an image. This is useful for cards that have a visual element, such as a product image or a photo.
Loading...
import { Card, CardContent } from '@camp/card';
import { Chip } from '@camp/chip';
import { Button } from '@camp/button';
<Card>
<CardContent
title="Title"
paragraph="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labe et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
renderImage={() => <img src="https://picsum.photos/id/76/180/250" alt="Bicycle" />}
renderChips={() => (
<div style={{ display: 'flex', gap: Space[2] }}>
<Chip type="default" color="ac-blue">
Chip
</Chip>
<Chip type="default" color="moss">
Chip
</Chip>
</div>
)}
renderActions={() => (
<div style={{ display: 'flex', gap: Space[3] }}>
<Button size="small" appearance="secondary">
Card action
</Button>
<Button size="small" appearance="secondary">
Card action
</Button>
</div>
)}
/>
</Card>;Actionable card
An actionable card can be interacted with. Hover/click on the card below to see. <Card> also accepts all HTML div attributes, so you can pass event handlers such as onClick to the card itself. Dismiss props are not supported on actionable cards.
Loading...
import { Card, CardContent } from '@camp/card';
<Card actionable>
<CardContent
title="Title"
paragraph="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labe et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
/>
</Card>;Disabled Card
You can make an actionable card disabled, preventing interaction.
Note: you will need to manually set any buttons to be disabled within it.
Loading...
import { Card, CardContent } from '@camp/card';
<Card actionable disabled>
<CardContent
title="Title"
paragraph="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labe et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
/>
</Card>;Selected Card
You can add a selected state to an actionable card.
Loading...
import { Card, CardContent } from '@camp/card';
<Card actionable selected>
<CardContent
title="Title"
paragraph="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labe et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
/>
</Card>;With Dismiss
You can also add a dismiss button to the card with the onDismiss prop. This is useful for cards that are not actionable, but you want to give the user the option to dismiss them. The dismissLabel prop is required when using onDismiss to add a label to the button for accessibility.
Loading...
import { Card, CardContent } from '@camp/card';
<Card onDismiss={() => alert('Card dismissed')} dismissLabel="Dismiss">
<CardContent
title="Title"
paragraph="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labe et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
/>
</Card>;Production Examples
More states from @camp/card's visual test stories, ranked by production
usage. Variations already documented above are not repeated here.
Ai Actionable
import { Card } from '@camp/card';
const defaultArgs = {
children: CustomContent(),
};
const CardContentWithActionableContent = (
<CardContent
title="Title"
paragraph={paragraph}
renderChips={renderChips}
renderImage={renderImage}
/>
);
<Card {...defaultArgs} actionable ai>{CardContentWithActionableContent}</Card>Ai Actionable Selected
import { Card } from '@camp/card';
const defaultArgs = {
children: CustomContent(),
};
const CardContentWithActionableContent = (
<CardContent
title="Title"
paragraph={paragraph}
renderChips={renderChips}
renderImage={renderImage}
/>
);
<Card
{...defaultArgs}
actionable
ai
selected
>
{CardContentWithActionableContent}
</Card>Ai Actionable Disabled
import { Card } from '@camp/card';
const defaultArgs = {
children: CustomContent(),
};
const CardContentWithActionableContent = (
<CardContent
title="Title"
paragraph={paragraph}
renderChips={renderChips}
renderImage={renderImage}
/>
);
<Card
{...defaultArgs}
actionable
ai
disabled
>
{CardContentWithActionableContent}
</Card>Ai Actionable Disabled Custom Content Not Configured
import { Card } from '@camp/card';
const defaultArgs = {
children: CustomContent(),
};
<Card {...defaultArgs} actionable ai disabled />Ai Actionable Disabled Custom Content Configured
import { Card } from '@camp/card';
const CustomContent = (disabled?: boolean) => (
<div>
<div
style={{ display: 'flex', flexDirection: 'column', gap: Space[2], paddingBottom: Space[3] }}
>
<Text color={disabled ? 'disabled' : undefined} appearance="body-small" as="p">
Here is some custom card content. It is sized to fit its content, or it will accept inline
styles for setting size. This content is {!disabled && 'not'} configured for a disabled
state.
</Text>
<Text color={disabled ? 'disabled' : undefined} appearance="body-small" as="p">
DISMISS BUTTON: It is the consumer's responsibility to format it such that it doesn't
overlap with a dismiss button, if one exists.
</Text>
<Text color={disabled ? 'disabled' : undefined} appearance="body-small" as="p">
DISABLED STATE: It is also the consumer's responsibility to set the opacity or colors of
their custom inner elements to account for a disabled state, if one is needed for their
implementation.
</Text>
</div>
<div style={{ display: 'flex', gap: Space[3] }}>
<Button disabled={disabled}>Button</Button>
<IconButton disabled={disabled}>
<AddMedium title="title" />
</IconButton>
</div>
</div>
);
const defaultArgs = {
children: CustomContent(),
};
<Card {...defaultArgs} actionable ai disabled>{CustomContent(true)}</Card>Ai
import { Card } from '@camp/card';
const defaultArgs = {
children: CustomContent(),
};
const renderActions = () => (
<div style={{ display: 'flex', gap: Space[3] }}>
<Button size="small" appearance="secondary">
Card action
</Button>
<Button size="small" appearance="secondary">
Card action
</Button>
</div>
);
const renderMenu = () => (
<IconButton appearance="floating">
<MoreMenuSmall title="more menu" />
</IconButton>
);
const paragraph =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labe et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
<Card
{...defaultArgs}
ai
>
{(
<CardContent
title="Title"
paragraph={paragraph}
renderActions={renderActions}
renderMenu={renderMenu}
/>
)}
</Card>19 more variations in Storybook
AI Card
Loading...
The ai prop is used to create an AI card. When using the ai prop, the dismiss props are not allowed, since AI cards are not dismissable.
Static
<CardContent> can also be used within an ai card to render the content. The renderMenu prop is available for use on <CardContent> to render a menu, which is a common pattern for AI cards.
Loading...
import { Card, CardContent } from '@camp/card';
<Card ai>
<CardContent
title="Title"
paragraph="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labe et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
renderActions={renderActions}
renderMenu={renderMenu}
/>
</Card>;Actionable
Actionable AI cards have hover, focus, and active states. They also have a selected state. Dismiss props are not supported on actionable AI cards.
Loading...
Loading...
Loading...
import { Card, CardContent } from '@camp/card';
<Card ai actionable>
<CardContent title="Card 1" paragraph="Some descriptive text." />
</Card>
<Card ai actionable>
<CardContent title="Card 2" paragraph="Some descriptive text." />
</Card>
<Card ai actionable selected>
<CardContent title="Card 3" paragraph="This card is selected." />
</Card>Loading
The Loading AI card can be used by itself in place of content that is loading, or the loading state can be toggled on and off for the card itself. The size of the loading card will expand to fill the parent container (this differs from the static card, which is sized based on the inner content; size your parent container accordingly).
Title and medium loading indicator
Loading...
No title and small loading indicator
Loading...
Custom loading background color and custom content
Loading...
Any content passed as children to the Card will not be visible when the card is loading. It is replaced by any content passed as loadingContent.
You can also update the loading background color by passing a value for loadingBackgroundColor. The available colors are: bg-default, bg-educational, bg-disabled.
The helper component <CardLoadingContent> may be used to optionally render a text and/or loading indicator. Full prop descriptions for <CardLoadingContent> can be found in the Overview section above.
import { Card, CardContent } from '@camp/card';
<Card
ai
loading
loadingContent={<CardLoadingContent title="Loading..." loadingIndicatorSize="medium" />}
>
<CardContent title="Card 1" paragraph="Some descriptive text." />
</Card>;Usage
Best practices
✅ DO
- Do use to visually separate content that is meant to draw focus. Use cards with dropshadows for the most important information on the page.
Accessibility
Keyboard support
- You can tab into an actionable card and then into its content.
- You can tab into the dismiss in the card (last).
Labels
- The developer should set
dismissLabelto provide a translated aria-label for the dismiss "X" button.