VibeUI
ComponentsDocs
VibeUI

Components with taste, that you own. Copy the source, retint the tokens, keep every line.

Library

  • All components
  • Signature pieces
  • Text effects
  • Footers

Docs

  • Introduction
  • Installation
  • Theming
  • CLI

Project

  • GitHub
  • Agents and MCP
  • Issues
  • MIT license
© 2026 VibeUI · MIT licensed
  1. Components
  2. Signature
  3. Folder Preview

Folder Preview

Folder that opens to let its files rise out for a peek.

SignatureClient component

Install

bash
npx @agents-npm-packages/vibeui add folder-preview --dir ./src/components/ui

Import

tsx
import { FolderPreview } from "@/components/ui/folder-preview"

API

Generated from the component's TypeScript types, so it always matches the source. * marks a required prop.

<FolderPreview>

PropTypeDefaultDescription
title*string—Heading text.
files*string[]—File names to show.
classNamestring—Extra classes, merged with the component's own.

Source

src/components/ui/folder-preview.tsx
"use client";

import * as React from "react";
import { cn } from "@/lib/utils";

/** Folder that opens to let its files rise out for a peek. */
export function FolderPreview({
  title,
  files,
  className,
}: {
  title: string;
  files: string[];
  className?: string;
}) {
  const [open, setOpen] = React.useState(false);
  const papers = files.slice(0, 3);

  return (
    <button
      type="button"
      aria-expanded={open}
      aria-label={`${title}, ${files.length} items`}
      onClick={() => setOpen((v) => !v)}
      className={cn("group relative block h-44 w-full max-w-[260px] text-left", className)}
    >
      {/* back panel and tab */}
      <span
        aria-hidden
        className="absolute bottom-0 left-0 h-[104px] w-full rounded-lg bg-surface-sunken ring-1 ring-inset ring-line-strong"
      />
      <span
        aria-hidden
        className="absolute bottom-[100px] left-0 h-5 w-[84px] rounded-t-[10px] bg-surface-sunken ring-1 ring-inset ring-line-strong"
      />
      {/* papers: hidden behind the front panel until the folder opens */}
      {papers.map((file, i) => (
        <span
          key={file}
          aria-hidden
          className="absolute bottom-3 flex h-[88px] items-start rounded-md border border-line bg-surface px-3 pt-2.5 font-mono text-[10.5px] text-muted shadow-quiet transition-transform duration-500 ease-spring"
          style={{
            left: 14 + i * 4,
            right: 14 - i * 4,
            zIndex: i + 1,
            transitionDelay: `${i * 45}ms`,
            transform: open
              ? `translateY(${-(46 + i * 10)}px) rotate(${(i - 1) * 3.5}deg)`
              : "translateY(0)",
          }}
        >
          {file}
        </span>
      ))}
      {/* front panel */}
      <span
        className={cn(
          "absolute bottom-0 left-0 z-10 flex h-[84px] w-full items-end justify-between rounded-lg border border-line-strong bg-surface-muted p-4 shadow-quiet transition-[transform,box-shadow] duration-500 ease-out",
          open ? "shadow-lift [transform:perspective(600px)_rotateX(-6deg)]" : "group-hover:-translate-y-0.5",
        )}
        style={{ transformOrigin: "bottom" }}
      >
        <span>
          <span className="block text-sm font-medium tracking-[-0.01em] text-ink">{title}</span>
          <span className="mt-0.5 block text-xs text-muted">
            {files.length} {files.length === 1 ? "item" : "items"}
          </span>
        </span>
        <span
          aria-hidden
          className={cn(
            "size-2 rounded-full transition-colors duration-300",
            open ? "bg-accent" : "bg-line-strong",
          )}
        />
      </span>
    </button>
  );
}

Exports FolderPreview. Peer dependencies: clsx, tailwind-merge.

PreviousFlow FieldNextGooey Menu