Skip to content
ssntl/ui

Flip card

A true 3D turn with a back face of its own, so what it reveals is never mirrored.

ssntl store

Q 250

Gift card

Redeem code

Il10-O0S5-8B3Z

Installation

$ pnpm dlx shadcn@latest add @ssntl/flip-card

Usage

tsx
import { FlipCard } from "@/components/ui/flip-card"

<FlipCard flipped={flipped} front={<Front />} back={<Back />} />
<Button onClick={() => setFlipped(!flipped)}>Show the code</Button>

Rules

What this component expects from you, as it ships in components/ui/flip-card.md next to the code. The full file adds when to use it, keyboard, the UI foundations it applies and every shared rule.

components/ui/flip-card.md
## Rules for Flip card

- **Readable flips.** A true 3D turn with a back face of its own, so the code on the back reads the right way round.
- **The hidden face is inert.** Focus and screen readers only reach the side you can see.
- Give the flip a visible control with a label that says what it shows; under reduced motion the faces cross-fade.
Open the full rules file (opens in a new tab)

Code

components/ui/flip-card.tsx
import * as React from "react"

import { cn } from "@/lib/utils"

// Readable flips: a true 3D turn with a back face of its own, so what it
// reveals is never mirrored. The hidden face is inert, so focus and screen
// readers only reach the side you can see. Under reduced motion the faces
// cross-fade instead of turning.
function FlipCard({
  flipped,
  front,
  back,
  className,
  ...props
}: Omit<React.ComponentProps<"div">, "children"> & {
  flipped: boolean
  front: React.ReactNode
  back: React.ReactNode
}) {
  const face =
    "col-start-1 row-start-1 grid rounded-lg backface-hidden motion-reduce:transition-opacity motion-reduce:duration-(--duration-menu)"
  return (
    <div
      data-slot="flip-card"
      className={cn("grid perspective-distant", className)}
      {...props}
    >
      <div
        data-flipped={flipped || undefined}
        className="grid transition-[rotate] duration-(--duration-panel) ease-in-out transform-3d data-flipped:rotate-y-180 motion-reduce:transition-none motion-reduce:data-flipped:rotate-y-0"
      >
        <div
          inert={flipped}
          className={cn(face, flipped && "motion-reduce:opacity-0")}
        >
          {front}
        </div>
        <div
          inert={!flipped}
          className={cn(
            face,
            "rotate-y-180 motion-reduce:rotate-y-0",
            !flipped && "motion-reduce:opacity-0"
          )}
        >
          {back}
        </div>
      </div>
    </div>
  )
}

export { FlipCard }