cards
Book Flip Card
A 3D book open/close card: a base card with a cover panel that rotates open on hover via CSS perspective + rotateY(-80deg) (~0.5s ease). The transform-origin sits on the spine (left edge) so it mimics opening a book. Inner content is revealed beneath. CSS-only, no JS — works as a React Server Component. Cover maps to --primary / --primary-foreground; base to --background / --foreground.
Ported from eslam-hany (MIT)
Hello
Install
1. Register the namespace (once per project):
json
// components.json — register the @bottega namespace once
{
"registries": {
"@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
}
}2. Add the component:
bash
npx shadcn add @bottega/book-flip-cardUsage
Usagetsx
<BookFlipCard />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| coverLabel | string | Hover Me | Text shown on the closed cover |
| innerContent | string | Hello | Text shown inside once the cover opens |
Source
book-flip-card.tsxtsx
import styles from "./book-flip-card.module.css";
export interface BookFlipCardProps {
/** Text shown on the closed cover */
coverLabel?: string;
/** Text shown inside once the cover opens */
innerContent?: string;
}
/**
* A 3D book open/close card. The cover panel rotates open on hover (CSS-only,
* `perspective` + `rotateY(-80deg)`, spine at transform-origin: 0) revealing the
* inner content beneath it. No JS — pure CSS transition.
*
* Port of: https://uiverse.io/eslam-hany/selfish-bobcat-73 (MIT, eslam-hany)
*/
export function BookFlipCard({
coverLabel = "Hover Me",
innerContent = "Hello",
}: BookFlipCardProps) {
return (
// tabIndex+role make the card keyboard-openable: focus triggers the same
// `.book:focus-within .cover` flip as hover. aria-label surfaces the label
// (the cover is aria-hidden) + the revealed content to screen readers.
<div
className={styles.book}
tabIndex={0}
role="group"
aria-label={`${coverLabel}: ${innerContent}`}
>
<p className={styles.innerText}>{innerContent}</p>
<div className={styles.cover} aria-hidden="true">
<p className={styles.coverText}>{coverLabel}</p>
</div>
</div>
);
}
Dependencies
- @bottega/tokens