XDSCard@xds/core · Card
Preview coming soon

Usage

Card groups related content into a visually distinct container with a border and background. Use it for profile cards, settings panels, data summaries, or any content that needs to stand out from the page.

Best practices

GuidancePractices
DoTry a section first — only use a card when you need the visual separation of a border and elevated background.
DoKeep padding consistent across sibling cards so they align visually in a grid or list.
DoUse the muted variant for secondary content like tips, callouts, or background information.
DoPair a card with XDSLayout when you need a structured header, scrollable content, and footer with actions.
Don'tNest cards inside other cards — flatten the hierarchy or use a section instead.
Don'tUse color variants for status — use Banner or Badge for that. Color cards are for categorization.

Anatomy

ElementDescription
ContainerrequiredThe outer box with border, background, border-radius, and padding.
ContentrequiredAny children rendered inside the card. Often a stack of heading, text, and actions.

Import

ts
import {XDSCard} from '@xds/core/Card'

Props

PropTypeDescription
width
SizeValueWidth of the card (number = pixels, string = used as-is).
height
SizeValueHeight of the card (number = pixels, string = used as-is).
maxWidth
SizeValueMaximum width of the card.
minHeight
SizeValueMinimum height of the card.
children
ReactNodeContent to render inside the card.
padding
0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 (default: 4)Internal padding using the spacing scale.
variant
'default' | 'muted' | 'blue' | 'cyan' | 'gray' | 'green' | 'orange' | 'pink' | 'purple' | 'red' | 'teal' | 'yellow' (default: 'default')Background color variant. `default` uses the standard card background. `muted` uses the wash background for de-emphasised cards. The non-semantic variants use the corresponding `--color-<name>-background` token.

Examples

Common configurations, variations, and states.
Card — Callout
tsx
'use client';
import {XDSCard} from '@xds/core/Card';
import {XDSStack} from '@xds/core/Layout';
import {XDSHeading, XDSText} from '@xds/core/Text';
export default function CardCallout() {
return (
<XDSStack direction="horizontal" gap={4}>
<XDSCard width={270} variant="muted">
<XDSStack direction="vertical" gap={2}>
<XDSHeading level={3}>Tip</XDSHeading>
<XDSText type="body" color="secondary">
Use the muted variant for callouts or supplementary information.
</XDSText>
</XDSStack>
</XDSCard>
<XDSCard width={270} variant="muted">
<XDSStack direction="vertical" gap={2}>
<XDSHeading level={3}>Note</XDSHeading>
<XDSText type="body" color="secondary">
Muted cards work well in sidebars or help panels.
</XDSText>
</XDSStack>
</XDSCard>
</XDSStack>
);
}
Card — Layout
tsx
'use client';
import {XDSCard} from '@xds/core/Card';
import {
XDSLayout,
XDSLayoutHeader,
XDSLayoutContent,
XDSLayoutFooter,
XDSHStack,
} from '@xds/core/Layout';
import {XDSButton} from '@xds/core/Button';
import {XDSHeading, XDSText} from '@xds/core/Text';
export default function CardWithInnerLayout() {
return (
<XDSCard width={380}>
<XDSLayout
header={
<XDSLayoutHeader hasDivider>
<XDSHeading level={3}>Edit Profile</XDSHeading>
</XDSLayoutHeader>
}
content={
<XDSLayoutContent>
<XDSText type="body" color="secondary">
Update your display name, bio, and profile photo. Changes are
saved immediately.
</XDSText>
</XDSLayoutContent>
}
footer={
<XDSLayoutFooter hasDivider>
<XDSHStack gap={2} hAlign="end">
<XDSButton label="Cancel" variant="ghost" />
<XDSButton label="Save changes" variant="primary" />
</XDSHStack>
</XDSLayoutFooter>
}
/>
</XDSCard>
);
}
Card — Simple
tsx
'use client';
import {XDSCard} from '@xds/core/Card';
import {XDSStack} from '@xds/core/Layout';
import {XDSHeading, XDSText} from '@xds/core/Text';
export default function CardWithSimpleContent() {
return (
<XDSCard width={360}>
<XDSStack direction="vertical" gap={2}>
<XDSHeading level={3}>Project Overview</XDSHeading>
<XDSText type="body" color="secondary">
This project tracks the redesign of the onboarding flow. The goal is
to reduce drop-off by 15% in Q2.
</XDSText>
<XDSText type="supporting" color="secondary">
Last updated 2 hours ago
</XDSText>
</XDSStack>
</XDSCard>
);
}
Card — Variants
tsx
'use client';
import {XDSCard} from '@xds/core/Card';
import {XDSStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
const VARIANTS = [
{variant: 'default' as const, label: 'General', note: '4 tasks due today'},
{
variant: 'muted' as const,
label: 'Archived',
note: 'No activity in 30 days',
},
{variant: 'blue' as const, label: 'Engineering', note: '12 open issues'},
{variant: 'green' as const, label: 'Marketing', note: '3 campaigns active'},
{variant: 'orange' as const, label: 'Urgent', note: '2 items need review'},
{variant: 'purple' as const, label: 'Design', note: '5 drafts in progress'},
];
export default function CardVariants() {
return (
<XDSStack direction="horizontal" gap={3} style={{flexWrap: 'wrap'}}>
{VARIANTS.map(({variant, label, note}) => (
<XDSCard key={variant} variant={variant} width={160}>
<XDSStack direction="vertical" gap={1}>
<XDSText type="body" weight="bold">
{label}
</XDSText>
<XDSText type="supporting" color="secondary">
{note}
</XDSText>
</XDSStack>
</XDSCard>
))}
</XDSStack>
);
}
ClickableCardWithNestedButtonA product card that navigates on click but has an independent
tsx
'use client';
import {XDSClickableCard} from '@xds/core/ClickableCard';
import {XDSStack} from '@xds/core/Layout';
import {XDSText, XDSHeading} from '@xds/core/Text';
import {XDSButton} from '@xds/core/Button';
export default function ClickableCardWithNestedButton() {
return (
<XDSClickableCard label="Product" href="/product/123" width={320}>
<XDSStack direction="vertical" gap={3}>
<XDSStack direction="vertical" gap={1}>
<XDSHeading level={4}>Wireless Headphones</XDSHeading>
<XDSText type="body" color="secondary">
$79.99
</XDSText>
</XDSStack>
<XDSButton label="Add to cart" onClick={() => {}} variant="primary" />
</XDSStack>
</XDSClickableCard>
);
}
SelectableCardMultiMulti-select tag picker using color variant selectable cards with color-matched selection borders.
tsx
'use client';
import {useState} from 'react';
import {XDSSelectableCard} from '@xds/core/SelectableCard';
import {XDSStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
const tags = [
{id: 'react', name: 'React', variant: 'blue' as const},
{id: 'typescript', name: 'TypeScript', variant: 'cyan' as const},
{id: 'node', name: 'Node.js', variant: 'green' as const},
{id: 'python', name: 'Python', variant: 'yellow' as const},
{id: 'rust', name: 'Rust', variant: 'orange' as const},
{id: 'go', name: 'Go', variant: 'teal' as const},
];
export default function SelectableCardMulti() {
const [selected, setSelected] = useState(new Set(['react', 'typescript']));
return (
<XDSStack direction="horizontal" gap={2} wrap="wrap">
{tags.map(tag => (
<XDSSelectableCard
key={tag.id}
label={tag.name}
isSelected={selected.has(tag.id)}
variant={tag.variant}
onChange={isNow => {
setSelected(prev => {
const next = new Set(prev);
if (isNow) {
next.add(tag.id);
} else {
next.delete(tag.id);
}
return next;
});
}}
width={130}>
<XDSText type="body" weight="bold">
{tag.name}
</XDSText>
</XDSSelectableCard>
))}
</XDSStack>
);
}

Showcase source

tsx
'use client';
import {XDSCard} from '@xds/core/Card';
import {XDSStack} from '@xds/core/Layout';
import {XDSText, XDSHeading} from '@xds/core/Text';
export default function CardShowcase() {
return (
<XDSCard width={320}>
<XDSStack direction="vertical" gap={2}>
<XDSHeading level={4}>Card title</XDSHeading>
<XDSText type="body" color="secondary">
Cards group related content with a border and background. Use them for
profiles, settings panels, or data summaries.
</XDSText>
</XDSStack>
</XDSCard>
);
}