Card
- Stable
- WCAG 2.2 evidence
- RTL
A bordered surface that groups related content: an optional media area, a header with icon tile and title,
a body, free content and a footer with meta text and an action. Presentational: no state, no effects and no
"use client", so it renders in React Server Components. With href the title is a native link stretched over the card (one target, one clean link name), the card lifts on hover and
shows the focus ring on keyboard focus (WCAG 2.1.1, 2.4.7, 2.5.8). Selected and disabled are shown by more than colour: the
check icon with visually hidden text, and the dimmed aria-disabled card (1.4.1, 4.1.2). Text wraps (1.4.10, 1.4.12).
Click handlers need a client component: wrap the card, or use href. Use ListItem for rows of similar items and do not nest cards. Consumer duties: heading level, image alternatives (alt),
link wording, and locale-aware formatting of dates and numbers in the text.
import { Card } from "@nexera-ui/react";- 11
- examples
- 16
- props
- 4
- live controls
- 1
- platform
- 11
- WCAG criteria
- 5
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { Card } from "@nexera-ui/react";
<Card />Examples 10
The same examples as Storybook, rendered live. Open Code to copy one.
Interactive
Figma State=Interactive: with href the title is a link stretched over the card. Hover lifts it; Tab shows the focus ring.
import { Card } from "@nexera-ui/react";
<Card href="#" titleLevel={3} />;
Selected
Figma State=Selected (isSelected): tinted fill, brand stroke, check icon and the visually hidden text "Selected".
import { Card } from "@nexera-ui/react";
<Card isSelected />;
Disabled
Figma State=Disabled (isDisabled): dimmed and aria-disabled; the title is not a link.
import { Card } from "@nexera-ui/react";
<Card isDisabled href="#" />;
With media
Figma Media=Yes (media slot): a 140 px area above the content.
import { Card } from "@nexera-ui/react";
function MediaPlaceholder() {
return (
<div aria-hidden="true" className="flex size-full items-center justify-center">
<div className="size-[5.625rem] rounded-full bg-brand-subtle-hover" />
</div>
);
}
<Card media={<MediaPlaceholder />} href="#" />;
Optional parts
The footer exists when meta or action is given; the header when there is a title, an icon or the selected check.
import { Button, Card } from "@nexera-ui/react";
export function OptionalParts() {
return (
<div className="flex flex-col gap-3">
<Card title="Title only" />
<Card title="Title and body" body="Family cover up to Rs 1.5M." />
<Card title="With meta" body="Family cover up to Rs 1.5M." meta="Updated 2 Oct" />
<Card title="With a button action" action={<Button type="button">Download</Button>} />
</div>
);
}
Long content
Long and translated text wraps in the title, body and footer; the card grows.
import { Card } from "@nexera-ui/react";
<Card
title="Krankenversicherung fuer die ganze Familie einschliesslich Eltern und Schwiegereltern"
body="Familienschutz bis zu 1,5 Mio. Rs. mit Jubilee Life, einschliesslich Zahnbehandlung, Brillen und Vorsorgeuntersuchungen ab 2026."
meta="Zuletzt aktualisiert am 2. Oktober 2026 von der Personalabteilung"
action="Details ansehen"
href="#"
/>;
Right to left
Right-to-left: the icon tile moves to the start (right) edge and the footer action to the end (left).
import { Card } from "@nexera-ui/react";
<Card
title="التأمين الصحي"
body="تغطية للعائلة تصل إلى 1.5 مليون روبية مع جوبيلي لايف، وتشمل الوالدين ابتداءً من 2026."
meta="تم التحديث في 2 أكتوبر"
action="عرض التفاصيل"
/>;
Narrow container
A 14 rem container: the footer wraps and the text keeps wrapping.
import { Card } from "@nexera-ui/react";
<div className="max-w-56">
<Card href="#" />
</div>;
In a grid
Cards fill their grid cells; give the grid auto-fill columns so they reflow without media queries.
import { Card } from "@nexera-ui/react";
export function InAGrid() {
return (
<div className="grid grid-cols-[repeat(auto-fill,minmax(16rem,1fr))] gap-4">
<Card {...sample} href="#" titleLevel={3} />
<Card
{...sample}
title="Dental cover"
body="Checkups and fillings for the whole family."
href="#"
titleLevel={3}
/>
<Card
{...sample}
title="Life insurance"
body="Three times annual salary."
isSelected
titleLevel={3}
/>
</div>
);
}
Plan comparison
import { useState } from "react";
import { Button, Card, Segment, SegmentedControl } from "@nexera-ui/react";
function PlanComparisonDemo() {
const [billing, setBilling] = useState("monthly");
const [selected, setSelected] = useState("Studio");
const plans = [
{
name: "Starter",
price: 12,
detail: "For your next side project",
features: "3 projects · Community support",
},
{
name: "Studio",
price: 29,
detail: "For a growing creative team",
features: "Unlimited projects · Priority support",
},
{
name: "Scale",
price: 79,
detail: "For teams building together",
features: "Advanced permissions · Dedicated support",
},
];
return (
<div className="w-[52rem] max-w-full flex flex-col gap-5">
<SegmentedControl
aria-label="Billing period"
selectedKeys={[billing]}
onSelectionChange={(keys) => {
const value = [...keys][0];
if (value) setBilling(String(value));
}}
>
<Segment id="monthly" label="Monthly" />
<Segment id="yearly" label="Yearly · save 20%" />
</SegmentedControl>
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
{plans.map((plan) => (
<Card
key={plan.name}
title={plan.name}
titleLevel={3}
body={plan.detail}
isSelected={selected === plan.name}
meta={plan.features}
action={
<Button
variant={selected === plan.name ? "primary" : "secondary"}
onPress={() => {
setSelected(plan.name);
}}
>
{selected === plan.name ? "Selected" : `Choose ${plan.name}`}
</Button>
}
>
<p className="my-4 text-heading-xl">
${billing === "yearly" ? (plan.price * 0.8).toFixed(2) : plan.price}
<span className="text-body-default"> / month</span>
</p>
</Card>
))}
</div>
<p role="status" className="m-0 text-body-default">
{selected} selected ·{" "}
{billing === "yearly" ? "Billed yearly with 20% savings" : "Billed monthly"}.
</p>
</div>
);
}
export function PlanComparison() {
return <PlanComparisonDemo />;
}
Props 16
Press "Try it" on a card to load that prop into the playground.
16 props shown
hrefNexerastringNot set: the card is not a link.
Destination. The title becomes a native link stretched over the whole card. Renders in Server Components; for client-side routing wrap the card
in your router's link behaviour or pass a router-aware href.
targetNexerastringOnly with href.
Link target, for example _blank.
relNexerastringOnly with href.
Link relation, for example noopener.
downloadNexerastring | booleanOnly with href.
Downloads the target; the value is the suggested file name.
hrefLangNexerastringOnly with href.
Language of the target.
titleNexeraReactNodeHeading of the card. Required with href: it becomes the link text,
so it is the accessible name of the interactive card. Wraps instead of truncating.
Required with href: the title is the link text.
titleLevelNexera1 | 2 | 3 | 4 | 5 | 6Makes the title a heading of this level (<h2> to <h6>), so the cards of a page appear in the heading list. Without it
the title is plain text.
bodyNexeraReactNodeSupporting text. Shown when provided.
metaNexeraReactNodeSmall print at the start of the footer. The footer is shown when meta or action is provided.
actionNexeraReactNodeText or element at the end of the footer. On a card with href keep it plain
text (a link inside a link is invalid); on a static card it may be a Button or link. Shown when provided.
iconNexeraReactNodeIcon in the tile before the title. 18 px, decorative. Shown when provided.
mediaNexeraReactNodeMedia above the content. Pass an <img> (or Image) with className="size-full object-cover".
Replaces the plan's boolean isMedia: a flag alone cannot carry the picture. Decorative pictures use alt="".
childrenNexeraReactNodeFree content after the body (chips, a progress bar,...). Not in Figma.
isSelectedNexerabooleanThe card is the chosen one: tinted fill, brand stroke and a check icon. The state is also text
(selectedLabel), so it never depends on colour (WCAG 1.4.1). For choosing between options use CheckboxCard or RadioCard.
isDisabledNexerabooleanThe card is unavailable: dimmed, aria-disabled, and its title is not a link.
selectedLabelNexerastringVisually hidden text read after the title of a selected card. Translate it.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
6 direct · 5 supporting- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.4.1Use of ColorLevel A · tested directly
- 1.4.4Resize TextLevel AA · supporting test
- 1.4.11Non-text ContrastLevel AA · supporting test
- 1.4.12Text SpacingLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.4.4Link Purpose (In Context)Level A · tested directly
- 2.4.7Focus VisibleLevel AA · supporting test
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 3.1.2Language of PartsLevel AA · tested directly
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
<Card className="shadow-sm" />Used in blocks
- Setup checklistOnboarding
- Settings layoutApp shells and navigation
- Notification preferencesSettings and preferences
- SecuritySettings and preferences
- Feature showcaseMedia and content
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.