Card
A self-contained content surface. All cards support a white (default) or transparent surface property.
By default, content is rendered inside a <div>, but this can be overriden by passing any valid tag name or component with the as prop.
Examples
Media Card
A card for highlighting an image, some content, and associated labels/tags.
Any child content will be placed after the title, but before the meta and tags.

Title goes here
Reprehenderit in labore id in ad ullamco nisi reprehenderit ex voluptate tempor.

Title goes here
Adipisicing esse velit pariatur commodo exercitation do esse laboris consequat anim in ea. Consequat velit tempor fugiat dolor excepteur id dolore sunt commodo dolore mollit do laborum. Lorem laborum excepteur duis quis minim qui tempor velit id id.
Dolore est excepteur qui dolor ipsum occaecat officia anim Lorem pariatur et et mollit ad. Incididunt nostrud excepteur eiusmod excepteur esse. Velit sint nulla proident occaecat est consequat reprehenderit Lorem enim aliquip.
import { CardImage } from '@curology/ui-components-web/react/Card/CardImage';
import { CardMeta } from '@curology/ui-components-web/react/Card/CardMeta';
import { CardTags } from '@curology/ui-components-web/react/Card/CardTags';
import { Label } from '@curology/ui-components-web/react/Label';
import { MediaCard } from '@curology/ui-components-web/react/Card/MediaCard';
import { MediaCardContent } from '@curology/ui-components-web/react/Card/MediaCard/MediaCardContent';
import { Tag } from '@curology/ui-components-web/react/Tag';
<MediaCard>
<MediaCardContent
label={<Label>Default</Label>}
title="Title goes here"
image={
<CardImage
aspectRatio="3:4"
label={<Label>Default</Label>}
src={exampleImage}
/>
}
tags={
<CardTags title="Tags">
<Tag>Meta tag</Tag>
<Tag>Meta tag</Tag>
</CardTags>
}
meta={<MediaCardMeta>5 min read</MediaCardMeta>}
>
Body copy goes here
</MediaCardContent>
</MediaCard>
Before & After Card
A card specifically for comparing two images. Supports a rating prop.
Any child content will be placed after the rating, but before the tags.


Title goes here
Reprehenderit in labore id in ad ullamco nisi reprehenderit ex voluptate tempor.


Title goes here
Adipisicing esse velit pariatur commodo exercitation do esse laboris consequat anim in ea. Consequat velit tempor fugiat dolor excepteur id dolore sunt commodo dolore mollit do laborum. Lorem laborum excepteur duis quis minim qui tempor velit id id.
import { BeforeAfterCard } from '@curology/ui-components-web/react/Card/BeforeAfterCard';
import { BeforeAfterCardContent } from '@curology/ui-components-web/react/Card/BeforeAfterCard/BeforeAfterCardContent';
import { BeforeAfterCardImages } from '@curology/ui-components-web/react/Card/BeforeAfterCard/BeforeAfterCardImages';
import { CardMeta } from '@curology/ui-components-web/react/Card/CardMeta';
import { CardTags } from '@curology/ui-components-web/react/Card/CardTags';
import { Label } from '@curology/ui-components-web/react/Label';
import { Tag } from '@curology/ui-components-web/react/Tag';
<BeforeAfterCard>
<BeforeAfterCardContent
title="Title goes here"
images={
<BeforeAfterCardImages
beforeProps={{
src: exampleImage,
}}
afterProps={{
src: exampleImage,
}}
/>
}
rating={<CardRating readOnly count="12,345" value="4.25" />}
tags={
<CardTags>
<Tag>Meta tag</Tag>
<Tag>Meta tag</Tag>
</CardTags>
}
meta={<BeforeAfterCardMeta>Some meta</BeforeAfterCardMeta>}
>
Body copy goes here
</BeforeAfterCardContent>
</BeforeAfterCard>
Control Card
A card to use as part of a form flow, like product or option selection.
Any child content will be placed after the meta.
When the fullyClickable property is passed to ControlCardContent, clicking anywhere on the card will toggle the checkbox.
Do not use this flag if there are additional controls inside the card.
The ControlCard should not be used with surface="transparent".

Headline
Select moisturizer
- The Custom Cleanserᴿˣ
- The Custom Formulaᴿˣ
- The moisturizer
Highlight
import { CardFooter } from '@curology/ui-components-web/react/Card/CardFooter';
import { CardImage } from '@curology/ui-components-web/react/Card/CardImage';
import { CardPricing } from '@curology/ui-components-web/react/Card/CardPricing';
import { ControlCardCard } from '@curology/ui-components-web/react/Card/ControlCardCard';
import { ControlCardCardContent } from '@curology/ui-components-web/react/Card/ControlCardCard/ControlCardCardContent';
import { ControlCardCardRadioButton } from '@curology/ui-components-web/react/Card/ControlCardCard/ControlCardCardRadioButton';
import { ControlCardCardRadioButtonGroup } from '@curology/ui-components-web/react/Card/ControlCardCard/ControlCardCardRadioButtonGroup';
import { Label } from '@curology/ui-components-web/react/Label';
import { List } from '@curology/ui-components-web/react/List';
import { NumberInput } from '@curology/ui-components-web/react/NumberInput';
<ControlCard className="cur-w-1/2">
<ControlCardContent
fullyClickable
input={<Checkbox />}
label={<Label>Label</Label>}
title="Headline"
image={<ControlCardImage aspectRatio="3:4" src={exampleImage} />}
meta={
<>
<CardPricing price="$0.00" salePrice="$0.00" subscription="mo" />
<Typography size="meta">Some additional copy</Typography>
</>
}
rating={<CardRating readOnly count="12,345" value="4.25" />}
>
<Typography size="meta" className="cur-text-subtle">
Ad irure id laboris laborum Lorem sunt velit consequat. Lorem eiusmod
ullamco velit mollit esse ex qui enim nostrud commodo non
exercitation. Culpa consequat eiusmod voluptate pariatur. Adipisicing
eiusmod dolore laboris laborum anim incididunt. Laboris reprehenderit
anim sint consectetur deserunt ullamco Lorem dolor cillum commodo
irure pariatur ad in. Veniam reprehenderit cupidatat laboris tempor et
dolore non voluptate qui occaecat nulla dolore do ex.
</Typography>
<Highlight state="success" icon={<PlaceholderIcon />}>
Highlight
</Highlight>
</ControlCardContent>
</ControlCard>
Product Card
A detailed product description with a clear call to action at the bottom.
Any child content will be placed after the rating.

Headline
Ad irure id laboris laborum Lorem sunt velit consequat. Lorem eiusmod ullamco velit mollit esse ex qui enim nostrud commodo non exercitation.
Ingredients
- The Custom Cleanserᴿˣ
Helps smooth texture and fine lines while brightening skin
- The Custom Formulaᴿˣ
Helps smooth texture and fine lines while brightening skin
- The moisturizer
Helps smooth texture and fine lines while brightening skin
Highlight

Headline
Ad irure id laboris laborum Lorem sunt velit consequat. Lorem eiusmod ullamco velit mollit esse ex qui enim nostrud commodo non exercitation.
Ingredients
- The Custom Cleanserᴿˣ
Helps smooth texture and fine lines while brightening skin
- The Custom Formulaᴿˣ
Helps smooth texture and fine lines while brightening skin
- The moisturizer
Helps smooth texture and fine lines while brightening skin
Highlight
import { Button } from '@curology/ui-components-web/react/Button';
import { CardFooter } from '@curology/ui-components-web/react/Card/CardFooter';
import { CardPricing } from '@curology/ui-components-web/react/Card/CardPricing';
import { CardRating } from '@curology/ui-components-web/react/Card/CardRating';
import { Highlight } from '@curology/ui-components-web/react/Highlight';
import { List } from '@curology/ui-components-web/react/List';
import { ListHeader } from '@curology/ui-components-web/react/ListHeader';
import { ListItem } from '@curology/ui-components-web/react/List/ListItem';
import { ProductCard } from '@curology/ui-components-web/react/Card/ProductCard';
import { ProductCardContent } from '@curology/ui-components-web/react/Card/ProductCard/ProductCardContent';
import { ProductCardImage } from '@curology/ui-components-web/react/Card/ProductCard/ProductCardImage';
import { Typography } from '@curology/ui-components-web/react/Typography';
<ProductCard className="cur-w-1/2">
<ProductCardContent
title="Headline"
image={
<ProductCardImage
aspectRatio="3:4"
label={<Label>Label</Label>}
src={exampleImage}
/>
}
meta={
<>
<Typography size="meta">Some additional copy</Typography>
<CardPricing price="$0.00" salePrice="$0.00" subscription="mo" />
</>
}
rating={<CardRating value={4.2} count={3215} />}
>
<Typography className="cur-text-subtle">
Ad irure id laboris laborum Lorem sunt velit consequat. Lorem eiusmod
ullamco velit mollit esse ex qui enim nostrud commodo non
exercitation.
</Typography>
<ListHeader headerTitle="Ingredients" />
<List className="cur-flex-1" size="xl">
<ListItem
titleEnd="0.0005%"
title="The Custom Cleanserᴿˣ"
iconEnd={<PlaceholderIcon />}
>
Helps smooth texture and fine lines while brightening skin
</ListItem>
<ListItem
titleEnd="0.0005%"
title="The Custom Formulaᴿˣ"
iconEnd={<PlaceholderIcon />}
>
Helps smooth texture and fine lines while brightening skin
</ListItem>
<ListItem
titleEnd="0.0005%"
title="The moisturizer"
iconEnd={<PlaceholderIcon />}
>
Helps smooth texture and fine lines while brightening skin
</ListItem>
</List>
<Highlight state="success" icon={<PlaceholderIcon />}>
Highlight
</Highlight>
<CardFooter>
<Button>Default</Button>
</CardFooter>
</ProductCardContent>
</ProductCard>
Text Card
A text-only card. Any child content will be placed after the title.
Small Text
Esse aute adipisicing irure ea labore reprehenderit nostrud.
Medium Text
Esse aute adipisicing irure ea labore reprehenderit nostrud.
Large Text
Esse aute adipisicing irure ea labore reprehenderit nostrud.
import { Accordion } from '@curology/ui-components-web/react/Accordion';
import { AccordionContent } from '@curology/ui-components-web/react/Accordion/AccordionContent';
import { AccordionSummary } from '@curology/ui-components-web/react/Accordion/AccordionSummary';
import { Button } from '@curology/ui-components-web/react/Button';
import { CardFooter } from '@curology/ui-components-web/react/Card/CardFooter';
import { Label } from '@curology/ui-components-web/react/Label';
import { Tag } from '@curology/ui-components-web/react/Tag';
import { TextCard } from '@curology/ui-components-web/react/Card/TextCard';
import { TextCardContent } from '@curology/ui-components-web/react/Card/TextCard/TextCardContent';
import { TextCardLabels } from '@curology/ui-components-web/react/Card/TextCard/TextCardLabels';
import { TextCardMeta } from '@curology/ui-components-web/react/Card/TextCard/TextCardMeta';
import { TextCardPricing } from '@curology/ui-components-web/react/Card/TextCard/TextCardPricing';
import { TextCardTags } from '@curology/ui-components-web/react/Card/TextCard/TextCardTags';
import { Typography } from '@curology/ui-components-web/react/Typography';
<TextCard size="sm|md (default)|lg" className="cur-w-[38rem] cur-mb-4">
<TextCardContent title="Medium Text">
<Typography size="body-lg">
Esse aute adipisicing irure ea labore reprehenderit nostrud.
</Typography>
<TextCardMeta>
<Typography size="meta">Some additional copy</Typography>
</TextCardMeta>
<TextCardPricing price="$0.00" salePrice="$0.00" subscription="mo" />
<TextCardLabels>
<Label size="lg">Label</Label>
<Label size="lg">Label</Label>
<Label size="lg">Label</Label>
<Label size="lg">Label</Label>
</TextCardLabels>
<div className="cur-mt-12 cur-mb-6">
<Accordion>
<AccordionSummary>Accordion</AccordionSummary>
<AccordionContent>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
sapien enim, rutrum quis tristique ut, ultrices vitae nibh. Sed
feugiat orci vel hendrerit accumsan
</AccordionContent>
</Accordion>
<Accordion>
<AccordionSummary>Accordion</AccordionSummary>
<AccordionContent>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
sapien enim, rutrum quis tristique ut, ultrices vitae nibh. Sed
feugiat orci vel hendrerit accumsan
</AccordionContent>
</Accordion>
<Accordion>
<AccordionSummary>Accordion</AccordionSummary>
<AccordionContent>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
sapien enim, rutrum quis tristique ut, ultrices vitae nibh. Sed
feugiat orci vel hendrerit accumsan
</AccordionContent>
</Accordion>
</div>
<TextCardTags title="Tags">
<Tag>Meta tag</Tag>
<Tag>Meta tag</Tag>
</TextCardTags>
<CardFooter>
<Button>Default</Button>
</CardFooter>
</TextCardContent>
</TextCard>
Basic Card Building Blocks
Custom card layouts can be composed using the various card components together.
import { Card } from '@curology/ui-components-web/react/Card';
<Card>
Just a white box!
</Card>

Now we're talkin'!

Pretty cool that the footer of the other card sticks to the bottom even though it's shorter...
import { Button } from '@curology/ui-components-web/react/Button';
import { Card } from '@curology/ui-components-web/react/Card';
import { CardContent } from '@curology/ui-components-web/react/Card/CardContent';
import { CardFooter } from '@curology/ui-components-web/react/Card/CardFooter';
import { CardImage } from '@curology/ui-components-web/react/Card/CardImage';
<Card>
<CardContent
image={<CardImage label={<Label>Default</Label>} src={exampleImage} />}
>
Now we're talkin'!
</CardContent>
<CardFooter>
<Button className="cur-w-full">CTA</Button>
<Button className="cur-w-full" buttonType="secondary">
CTA <ButtonMeta>Secondary</ButtonMeta>
</Button>
</CardFooter>
</Card>

Tempor pariatur ut minim nulla proident dolor proident quis magna est deserunt dolore. Eu nulla dolor labore quis mollit fugiat cillum ullamco deserunt ea anim ex cupidatat.
import { Card } from '@curology/ui-components-web/react';
<Card
surface="transparent"
>
...
</Card>
Props
CardProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
id | string | '' | No | A unique ID for this card. |
surface | "white" | "transparent" | 'white' | No | The surface color |
CardContentProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
image | ReactElement<any, string | JSXElementConstructor<any>> | No | An (optional) image to display to display at the top (or side) or the card. |
CardImageProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
aspectRatio | "auto" | "square" | "1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "4:5" | "5:4" | 'square' | No | Constrain the image to a specific aspect ratio. |
label | ReactNode | No | A label to overlay on the header content. | |
labelAlign | "left" | "right" | 'left' | No | Which side should the label be aligned to? |
CardPricingProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
price | string | number | Yes | The price of the product. | |
salePrice | string | number | No | The sale price of the product. | |
salePriceSubscription | string | No | A sale-specific subscription string. If `undefined`, will default to `subscription`. If an empty string, will be blank. | |
subscription | string | No | A string to prepend to the prices like "mo" for "$12/mo". |
CardRatingProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
count | string | number | No | A number to display next to the rating. | |
defaultValue | string | number | No | The default value of the rating. | |
onChange | FormControlChangeEventHandler | No | Emit a new value when the input's selected value changes. | |
readOnly | false | true | false | No | Can the field be interacted with? |
showCount | false | true | No | ||
size | "md" | "sm" | 'md' | No | The size of the field. |
value | string | number | No | The current value of the rating. |
BeforeAfterCardProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
id | string | '' | No | A unique ID for this card. |
surface | "white" | "transparent" | 'white' | No | The surface color |
BeforeAfterCardContentProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
images | ReactElement<any, string | JSXElementConstructor<any>> | Yes | A set of images to display side-by-side. | |
meta | ReactNode | No | A bit of text to render below the title and before the content. | |
rating | ReactElement<any, string | JSXElementConstructor<any>> | No | A star-based rating. | |
tags | ReactElement<any, string | JSXElementConstructor<any>> | No | A tag, or list of tags, to be rendered stuck to the bottom of the card. | |
title | ReactNode | No | Text (or a component with text) to be rendered in bold. |
BeforeAfterCardImagesProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
afterProps | Omit<CardImageProps, "aspectRatio"> | No | Any props to apply to the after image. | |
aspectRatio | "auto" | "square" | "1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "4:5" | "5:4" | '3:4' | No | An aspect ratio to enforce on the images. |
beforeProps | Omit<CardImageProps, "aspectRatio"> | No | Any props to apply to the after image. |
ControlCardProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
id | string | '' | No | A unique ID for this card. |
surface | "white" | "transparent" | 'white' | No | The surface color |
ControlCardContentProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
fullyClickable | false | true | false | No | Should the full card be clickable? |
image | ReactElement<any, string | JSXElementConstructor<any>> | No | An (optional) image to display to display at the top (or side) or the card. | |
input | ReactNode | No | The input to render. | |
label | ReactNode | No | Label to render at the top of the card, above the title. | |
meta | ReactNode | No | A bit of text to render after the title. | |
rating | ReactElement<any, string | JSXElementConstructor<any>> | No | A star-based rating. |
ControlCardImageProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
aspectRatio | "auto" | "square" | "1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "4:5" | "5:4" | 'square' | No | Constrain the image to a specific aspect ratio. |
label | ReactNode | No | A label to overlay on the header content. | |
labelAlign | "left" | "right" | 'left' | No | Which side should the label be aligned to? |
ControlCardRadioButtonProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
alwaysShowChildren | false | true | false | No | Should the children display even if `checked=false`? |
indeterminate | false | true | false | No | Show a partially selected checkbox when `checked=true`. |
label | ReactNode | No | A string or component to display as a label above the input | |
meta | ReactNode | No | A string or component to display as meta text beneath the input | |
onChange | FormControlChangeEventHandler | No | Emit a new value when the input's selected value changes. | |
recommended | false | true | No | Render recommended label along with radio button item | |
size | "md" | "sm" | 'md' | No | The size of the field |
state | "" | "critical" | "warning" | "success" | '' | No | The state of the field |
ControlCardRadioButtonGroupProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
brand | "agency" | "curology" | '' | No | The brand of the component. |
defaultValue | string | number | readonly string[] | (string | number | readonly string[] | undefined)[] | No | Default value to use the first time the context is created. | |
disabled | false | true | false | No | Should the field be considered disabled? |
fieldClassName | string | No | A className to apply to the field. | |
fieldProps | Record<string, unknown> | No | Any props to spread onto the field container. | |
helperText | ReactNode | No | A string or component to display as helper text beneath the input | |
highlight | ReactNode | No | Highlighted helper text to display at the bottom of the multiline text input | |
highlightClassName | string | No | A className to apply to the highlight. | |
id | string | '' | No | A unique ID for this form control. |
label | ReactNode | No | A string or component to display as a label above the input | |
name | string | '' | No | The name for the radio group. |
onChange | FormControlChangeEventHandler<InputType, string | number | readonly string[] | (string | number | readonly string[] | undefined)[] | undefined> & ((evt: ChangeEvent<...>, value: string | ... 2 more ... | undefined) => void) | No | Emit a new value when the input's selected value changes. Emit a new value when the group's selected values change. | |
required | false | true | false | No | Is the field required? |
resize | false | true | false | No | Enable or disable resizing. |
size | "md" | "sm" | 'md' | No | The size of the field. |
state | "" | "critical" | "warning" | "success" | '' | No | The state of the field |
type | "number" | "image" | "search" | "button" | "time" | "text" | "color" | "hidden" | string & {} | "submit" | "reset" | "checkbox" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "radio" | "range" | "tel" | "url" | "week" | 'text' | No | The type of the input. |
value | string | number | readonly string[] | string & readonly string[] | number & readonly string[] | readonly string[] & string | readonly string[] & number | (string | number | readonly string[] | undefined)[] & string | (string | number | readonly string[] | undefined)[] & number | (string | number | readonly string[] | undefined)[] & readonly string[] | '' | No | The currently selected value. |
MediaCardProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
id | string | '' | No | A unique ID for this card. |
surface | "white" | "transparent" | 'white' | No | The surface color |
MediaCardContentProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
image | ReactElement<any, string | JSXElementConstructor<any>> | No | An (optional) image to display to display at the top (or side) or the card. | |
label | ReactElement<any, string | JSXElementConstructor<any>> | No | A label, or list of labels, to be rendered below the image. | |
meta | ReactNode | No | A bit of text to render below the title and before the content. | |
tags | ReactElement<any, string | JSXElementConstructor<any>> | No | A tag, or list of tags, to be rendered stuck to the bottom of the card. |
ProductCardProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
id | string | '' | No | A unique ID for this card. |
surface | "white" | "transparent" | 'white' | No | The surface color |
ProductCardContentProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
horizontal | false | true | No | Displays horizontal card styling. | |
image | ReactElement<any, string | JSXElementConstructor<any>> | No | An (optional) image to display to display at the top (or side) or the card. | |
meta | ReactNode | No | A bit of text to render after the title. | |
rating | ReactElement<any, string | JSXElementConstructor<any>> | No | A star-based rating. | |
title | ReactNode | No | Text (or a component with text) to be rendered in bold. |
ProductCardImageProps
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
aspectRatio | "auto" | "square" | "1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "4:5" | "5:4" | 'square' | No | Constrain the image to a specific aspect ratio. |
label | ReactNode | No | A label to overlay on the header content. | |
labelAlign | "left" | "right" | 'left' | No | Which side should the label be aligned to? |