Card
A surface for content that belongs together, with a larger radius than a control so it never reads as a button.
Card
server - 0 KBA surface for content that belongs together, with a larger radius than a control so it never reads as a button.
Storage
The code
import { CardBody, CardHeader, CardRoot } from 'topclassui/card'
/** The first drawing: a title and a line of text, with nothing to press. */
export default function Demo() {
return (
<div className="max-w-xs">
<CardRoot>
<CardHeader>
<h2>Storage</h2>
</CardHeader>
<CardBody>You have used 18 GB of the 50 GB in your plan.</CardBody>
</CardRoot>
</div>
)
}
Card sets the type of its title and leaves the heading level to you: put the h2 or h3 inside CardHeader.
Every state
3 statesStorage
Version 0.4
Import from the package
import { CardRoot, CardHeader, CardBody, CardFooter, CardSkeleton } from 'topclassui/card'Install once:npm i topclassui@beta zod
Examples
- Every part is a flat export:
CardRoot,CardHeader,CardBody,CardFooterandCardSkeleton. There is noCard. - A card is not interactive itself; what the user can do goes in
CardFooteras a Button, in a row that wraps when the card is narrow. CardBodyis a block, so space several paragraphs yourself, for example withspace-y-2.- Card takes the width of its container: in a grid, each card fills its cell and the height of its row.
CardSkeletonholds the full width at 5:2 while data loads; pass the real height inclassName.
Storage
import { CardBody, CardHeader, CardRoot } from 'topclassui/card'
/** The first drawing: a title and a line of text, with nothing to press. */
export default function Demo() {
return (
<div className="max-w-xs">
<CardRoot>
<CardHeader>
<h2>Storage</h2>
</CardHeader>
<CardBody>You have used 18 GB of the 50 GB in your plan.</CardBody>
</CardRoot>
</div>
)
}
Version 0.4
import { Button } from 'topclassui/button'
import { CardBody, CardFooter, CardHeader, CardRoot } from 'topclassui/card'
/** The second drawing: a solid and an outline small button in the footer. */
export default function Demo() {
return (
<div className="max-w-xs">
<CardRoot>
<CardHeader>
<h2>Version 0.4</h2>
</CardHeader>
<CardBody>Card is now CardRoot. Find and replace; nothing else changed.</CardBody>
<CardFooter>
<Button size="sm">Read more</Button>
<Button size="sm" variant="outline" tone="neutral">Later</Button>
</CardFooter>
</CardRoot>
</div>
)
}
import { Button } from 'topclassui/button'
import { CardBody, CardFooter, CardHeader, CardRoot } from 'topclassui/card'
/** The docs code sample, with the download as a real link that keeps the button look. */
export default function Demo() {
return (
<div className="max-w-xs">
<CardRoot>
<CardHeader>
<h2>Monthly report</h2>
</CardHeader>
<CardBody>Revenue rose 12% against April, driven by renewals.</CardBody>
<CardFooter>
<Button render={<a href="/reports/may.pdf" />} variant="outline" size="sm">
Download PDF
</Button>
</CardFooter>
</CardRoot>
</div>
)
}
Before you migrate
Export your projects first. The archive keeps every file and its history.
Invitations that are still pending do not move, so send them again afterwards.
import { CardBody, CardHeader, CardRoot } from 'topclassui/card'
/** CardBody is a block, so the caller spaces several paragraphs, here with space-y-2. */
export default function Demo() {
return (
<div className="max-w-sm">
<CardRoot>
<CardHeader>
<h2>Before you migrate</h2>
</CardHeader>
<CardBody className="space-y-2">
<p>Export your projects first. The archive keeps every file and its history.</p>
<p>Invitations that are still pending do not move, so send them again afterwards.</p>
</CardBody>
</CardRoot>
</div>
)
}
Starter
Team
Business
import { CardBody, CardHeader, CardRoot } from 'topclassui/card'
/** Three cards in a grid: each takes the width of its cell and the height of its row. */
export default function Demo() {
return (
<div className="grid gap-4 sm:grid-cols-3">
<CardRoot>
<CardHeader>
<h2>Starter</h2>
</CardHeader>
<CardBody>One project and 5 GB of storage.</CardBody>
</CardRoot>
<CardRoot>
<CardHeader>
<h2>Team</h2>
</CardHeader>
<CardBody>Ten projects, 50 GB of storage and a shared library for the whole team.</CardBody>
</CardRoot>
<CardRoot>
<CardHeader>
<h2>Business</h2>
</CardHeader>
<CardBody>Unlimited projects and single sign-on.</CardBody>
</CardRoot>
</div>
)
}
Storage
import { CardBody, CardHeader, CardRoot, CardSkeleton } from 'topclassui/card'
/** The skeleton at its default 5:2, then one given h-36 above a real card of that height. */
export default function Demo() {
return (
<div className="flex max-w-xs flex-col gap-4">
<CardSkeleton />
<CardSkeleton className="h-36" />
<CardRoot className="h-36">
<CardHeader>
<h2>Storage</h2>
</CardHeader>
<CardBody>You have used 18 GB of the 50 GB in your plan.</CardBody>
</CardRoot>
</div>
)
}
التقرير الشهري
import { Button } from 'topclassui/button'
import { CardBody, CardFooter, CardHeader, CardRoot } from 'topclassui/card'
export default function Demo() {
return (
<div dir="rtl" lang="ar" className="max-w-xs">
<CardRoot>
<CardHeader>
<h2>التقرير الشهري</h2>
</CardHeader>
<CardBody>ارتفعت الإيرادات 12% مقارنة بشهر أبريل، بفضل التجديدات.</CardBody>
<CardFooter>
<Button size="sm">اقرأ المزيد</Button>
<Button size="sm" variant="outline" tone="neutral">لاحقًا</Button>
</CardFooter>
</CardRoot>
</div>
)
}
Props
CardRoot
CardHeader
CardBody
CardFooter
CardSkeleton
No props of its own: it takes the attributes of the element it renders.
Keyboard and accessibility
Nothing to operate: it is not interactive on its own.
- Card renders plain
divelements with no role: what it holds gives it meaning. - Pick the heading level the page outline needs;
CardHeaderstyles whatever heading you put in it. CardSkeletonis hidden from assistive technology, so mark the loading region itself, for example witharia-busy.
Right to left
The footer's buttons start at the inline start, so in Arabic the first one sits on the right; text aligns to the start.
Server and client
Renders on the server and ships no JavaScript.
Files: index.tsx.
On the chip: server - 0 KB.