Card
A bordered surface for grouping related content — a set of thin, styled wrappers that own layout only, leaving the content entirely to you.
Create a project
Deploy your new project in one click.
Usage#
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "neelam-ui";
<Card>
<CardHeader>
<CardTitle>Create a project</CardTitle>
<CardDescription>Deploy your new project in one click.</CardDescription>
</CardHeader>
<CardContent>…</CardContent>
<CardFooter>…</CardFooter>
</Card>Every part is optional and they compose in any order. A card with just
CardContent is perfectly normal.
The parts#
| Component | Element | Role |
|---|---|---|
Card | <div> | The bordered, rounded surface |
CardHeader | <div> | Stacks title and description with the right spacing |
CardTitle | <h3> | The heading |
CardDescription | <p> | Supporting text under the title |
CardContent | <div> | The body |
CardFooter | <div> | Actions, usually buttons |
This is the same layout-versus-content split
Dialog's DialogHeader and
Message draw for their own families: the wrappers
own spacing and alignment, never what goes inside.
Heading levels#
CardTitle renders an <h3>. That is a sensible default, not a claim about
your page — heading levels must descend without skipping to be useful for
navigation, and a card does not know what surrounds it.
There is no as prop. When the outline calls for a different level, render the
heading yourself inside CardHeader — the styling is three utility classes:
<CardHeader>
<h2 className="text-lg leading-none font-semibold tracking-tight">
Create a project
</h2>
<CardDescription>Deploy your new project in one click.</CardDescription>
</CardHeader>Check the heading order on the page
A grid of cards inside an <h2> section wants <h3> titles — the default is
right. Cards directly under an <h1> want <h2>. Skipped levels are one of
the most common accessibility problems in card-heavy layouts.
Clickable cards#
There is no href or onClick on Card, deliberately. Making a whole card
clickable well is subtle: a <div onClick> is invisible to the keyboard, and
wrapping everything in an <a> swallows any buttons inside it.
The reliable pattern is to keep the card static and let one real link inside it cover the surface:
<Card className="relative">
<CardHeader>
<CardTitle>
<a href="/projects/1" className="after:absolute after:inset-0">
Project one
</a>
</CardTitle>
</CardHeader>
</Card>The link is the accessible name and the only tab stop; the pseudo-element gives it the card's whole hit area.
Keyboard#
| Key | Behaviour |
|---|---|
| Tab | Not focusable itself. Focus moves through whatever interactive content the card holds, in DOM order. |
Accessibility#
- Every part is a plain element with no ARIA role. A card is a visual grouping, and giving it a role would tell assistive tech something untrue.
CardTitleis a real heading, so it appears in the document outline and can be jumped to — which is what makes a page of cards navigable.- If a card needs to be a landmark or a labelled region, wrap it yourself in a
<section aria-labelledby>pointing at the title'sid. - Nothing here traps or reorders focus, so DOM order stays reading order.
API reference#
The card parts add no props of their own — each is a styled wrapper around a native element, and every attribute passes straight through. See the table above for which element each renders.