File List Icon View
Finder's icon view: big icons and thumbnails in a grid, with date groups, drag to select and drag to move.
@cupertinoui-pro/file-list-icon-viewCupertinoUI Pro"use client"
import * as React from "react"
import type { FileNode } from "@/registry/macos/lib/file-tree"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { FileListIconView } from "@/components/ui/file-list-icon-view"
// The same JSON as the other file lists; `thumbnail` shows an image
// instead of the icon.
const initialFiles: FileNode[] = [
{
name: "Design",
children: [
{ name: "Icons.sketch", kind: "Sketch Document", size: "18,2 MB" },
{ name: "Wallpaper.heic", kind: "HEIF Image", size: "4,1 MB" },
],
},
{ name: "Invoices", children: [{ name: "October.pdf", kind: "PDF Document", size: "198 KB" }] },
{ name: "Drift Light.webp", kind: "WebP Image", size: "52 KB", thumbnail: "/wallpapers/drift-light.webp" },
{ name: "Drift Dark.webp", kind: "WebP Image", size: "44 KB", thumbnail: "/wallpapers/drift-dark.webp" },
{ name: "Meeting notes from the October planning session.txt", kind: "Plain Text", size: "4 KB", tag: "orange" },
{ name: "Roadmap.key", kind: "Keynote", size: "2,4 MB" },
]
// Drag over empty space to select a rectangle, drag the selection onto a
// folder to move it, double-click a folder to go into it (⌘↑ comes back)
// and double-click a file to open it. Right-click for Finder's menu.
export default function FileListIconViewDemo() {
const [files, setFiles] = React.useState(initialFiles)
const [opened, setOpened] = React.useState<FileNode | null>(null)
return (
<div className="flex h-72 w-full max-w-2xl overflow-hidden rounded-[12px] bg-mac-content shadow-[0_0_0_1px_var(--mac-grid)]">
<FileListIconView files={files} onFilesChange={setFiles} onOpen={setOpened} className="flex-1" />
<Dialog open={opened !== null} onOpenChange={(open) => !open && setOpened(null)}>
<DialogContent className="sm:max-w-xs">
<DialogHeader>
<DialogTitle>{opened?.name}</DialogTitle>
<DialogDescription>
{[opened?.kind, opened?.size].filter(Boolean).join(" - ")}
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
</div>
)
}Installation
pnpm dlx shadcn@latest add @cupertinoui-pro/file-list-icon-viewPart of CupertinoUI Pro: the CLI installs it with your license key, once the @cupertinoui-pro registry is in components.json.
Usage
import { FileListIconView } from "@/components/ui/file-list-icon-view"
import type { FileNode } from "@/lib/file-tree"
const files: FileNode[] = [
{ name: "Projects", children: [] },
{ name: "Wallpaper.webp", thumbnail: "/wallpaper.webp" },
]
<FileListIconView files={files} onFilesChange={setFiles} onOpen={openFile} />files is the same JSON as FileListTable and FileListColumns take, so one tree can switch views. The icon view shows one folder: double-click a folder (or ⌘↓) to go into it and ⌘↑ to go back out; folder and onFolderChange control which one.
Files with a thumbnail show the image, scaled to fit the 64px icon; others show renderIcon. Names wrap onto two lines like Finder's, breaking after spaces and hyphens, and long ones are shortened in the middle so the extension stays visible.
Drag over empty space to select every item the rectangle touches; with Shift or ⌘ held it adds and removes. Click selects, Shift-click a range and ⌘-click single items. The arrow keys move around the grid, up and down by row, also across groups.
With onFilesChange, drag the selection onto a folder to move it, and right-click for Finder's menu: Open, Move to Trash, Rename, Duplicate, Copy and the tags; on empty space, New Folder. Return renames, ⌘D duplicates, ⇧⌘N makes a folder and ⌘⌫ deletes.
With groupBy, each group gets a sticky header; the one at the top gets the toolbar's background. A group longer than a row can be collapsed to its first row with Show Less.
Examples
Groups
groupBy puts the files under sticky headers, like Finder's Use Groups. A group longer than a row can show just its first row.
"use client"
import type { FileNode } from "@/registry/macos/lib/file-tree"
import { FileListIconView } from "@/components/ui/file-list-icon-view"
const files: FileNode[] = [
{ name: "Drift Light.webp", kind: "Image", thumbnail: "/wallpapers/drift-light.webp" },
{ name: "Projects", kind: "Folder", children: [] },
{ name: "Drift Dark.webp", kind: "Image", thumbnail: "/wallpapers/drift-dark.webp" },
{ name: "Budget 2027.numbers", kind: "Document" },
{ name: "Archive", kind: "Folder", children: [] },
{ name: "Roadmap.key", kind: "Document" },
{ name: "Notes.txt", kind: "Document" },
]
// `groupBy` puts the files under sticky headers, in the order the groups
// first appear: here by kind, in Finder often by date.
export default function FileListIconViewGroups() {
return (
<div className="flex h-80 w-full max-w-2xl overflow-hidden rounded-[12px] bg-mac-content shadow-[0_0_0_1px_var(--mac-grid)]">
<FileListIconView files={files} groupBy={(file) => `${file.kind}s`} className="flex-1" />
</div>
)
}API Reference
Every part forwards the props of the underlying element. The props below are the ones CupertinoUI adds or changes.
FileNode
One file or folder in files: plain JSON, the same for every file list. Import the type from @/lib/file-tree.
| Prop | Type | Description |
|---|---|---|
name | string | The file name. |
id | string | Unique among its siblings, for keys; defaults to the name. Can't contain "/". |
children | FileNode[] | Makes it a folder; folders nest to any depth. Use [] for an empty folder. |
thumbnail | string | An image URL shown instead of the icon in the icon and gallery views, and as the preview. |
kind / size / created / modified | string | Shown as they are, so format them the way you want them displayed. |
info | Record<string, string> | More Information rows for the column and gallery previews, e.g. { Dimensions: "2007×2853" }. |
icon | string | A hint for renderIcon, e.g. "image" or "movie". |
tag | string | A Finder tag color: "red", "orange", "yellow", "green", "blue", "purple" or "gray". |
onOpen | (file: FileNode) => void | Called when this file or folder is opened: double-clicked, or ⌘O. |
FileListIconView
| Prop | Type | Description |
|---|---|---|
files | FileNode[] | The tree. The view shows one folder of it at a time. |
defaultFolder | string | nullDefault: null | The key of the folder shown first; null is the top of the tree. |
folder | string | null | The folder shown, controlled. Update it from onFolderChange. |
onFolderChange | (folder: string | null) => void | Called when a folder is opened, or ⌘↑ goes back up. |
multiple | booleanDefault: true | Lets people select several files, in a rectangle or with Shift and ⌘. false allows one. |
defaultSelected / selected | string[] | The keys of the selected files, uncontrolled or controlled. |
onSelectedChange | (keys: string[], files: FileNode[]) => void | Called when the selection changes. |
renderIcon | (file: FileNode) => ReactNode | The icon of a file without a thumbnail, drawn 64px large. Defaults to a folder or a document. |
onFilesChange | (files: FileNode[], change: FileChange) => void | Turns on editing: moving, Move to Trash, Rename, Duplicate, New Folder and tags. Gets the new tree to store, and what changed. |
onOpen | (file: FileNode, key: string) => void | Called when a file is opened: double-clicked, or ⌘O or ⌘↓. Folders open in the view. |
menuItems | (files: FileNode[], keys: string[]) => ReactNode | More ContextMenuItems for the right-click menu, after Finder's own. |
groupBy | (file: FileNode) => string | Puts each file under a header with this name; groups come in the order of their first file. |
className | string | Extra classes, merged with the component's own. |

