Flashcard

Study card for active recall with prompt and answer states.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/flashcard.json
bash

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

"use client";

import { useState } from "react";

import { RefreshCcw } from "lucide-react";
import type { ReactNode } from "react";

import { cn } from "../../lib/utils";
import { Badge } from "../badge";
import { Button } from "../button";
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "../card";

export type FlashcardProps = {
  answer: ReactNode;
  category?: string;
  className?: string;
  defaultFlipped?: boolean;
  hint?: string;
  onFlipChange?: (flipped: boolean) => void;
  question: ReactNode;
  title?: string;
};

export function Flashcard({
  answer,
  category,
  className,
  defaultFlipped = false,
  hint,
  onFlipChange,
  question,
  title = "Flashcard",
}: FlashcardProps): ReactNode {
  const [flipped, setFlipped] = useState(defaultFlipped);

  const toggleFlipped = (): void => {
    const nextValue = !flipped;
    setFlipped(nextValue);
    onFlipChange?.(nextValue);
  };

  return (
    <Card
      className={cn(
        "my-6 overflow-hidden border-primary/20 bg-gradient-to-br from-card to-primary/5",
        className,
      )}
    >
      <CardHeader className="flex flex-row items-center justify-between gap-3 border-b bg-background/80">
        <div className="space-y-1">
          <div className="flex items-center gap-2">
            <Badge variant="secondary">Study</Badge>
            {category ? <Badge variant="outline">{category}</Badge> : null}
          </div>
          <CardTitle>{title}</CardTitle>
        </div>
        <Button onClick={toggleFlipped} size="sm" variant="outline">
          <RefreshCcw className="mr-2 size-4" />
          Flip
        </Button>
      </CardHeader>
      <CardContent className="space-y-4 p-6">
        <div className="min-h-40 rounded-xl border bg-background p-6 shadow-sm">
          <p className="mb-2 text-xs font-medium uppercase tracking-[0.2em] text-muted-foreground">
            {flipped ? "Answer" : "Prompt"}
          </p>
          <div className="text-base text-foreground [&>p]:mb-3">
            {flipped ? answer : question}
          </div>
        </div>
        {hint ? (
          <p className="text-sm text-muted-foreground">Hint: {hint}</p>
        ) : null}
      </CardContent>
      <CardFooter className="justify-between border-t bg-background/80 px-6 py-4">
        <span className="text-sm text-muted-foreground">
          {flipped
            ? "Use this side to check your recall."
            : "Try answering before flipping the card."}
        </span>
        <Button onClick={toggleFlipped} variant="ghost">
          {flipped ? "Show prompt" : "Reveal answer"}
        </Button>
      </CardFooter>
    </Card>
  );
}
typescript

Dependencies

  • @vllnt/ui@^0.2.1