API reference

@thefinder/react

The file manager UI. Its only dependencies are react and react-dom (≥ 18, peer); styles are plain CSS.

"use client";
import { TheFinder } from "@thefinder/react";
import "@thefinder/react/styles.css";

<div style={{ height: "100vh" }}>
  <TheFinder endpoint="/api/files" locale="en" />
</div>

Import styles.css once, anywhere in your app. The component fills the height of its parent (change it with the height prop).

<TheFinder />#

Connection#

PropTypeDescription
endpointstringRequired. API URL, e.g. "/api/files"
headersRecord<string, string> \| () => Record<string, string>Headers added to every request (e.g. Authorization)
credentialsRequestCredentials"include" for cookies with a cross-origin API
clientTheFinderClientA client you configured yourself. When given, it is used instead of endpoint/headers/credentials.
onUnauthorized() => voidCalled when the API returns 401, e.g. to redirect to a login page

Appearance#

PropTypeDefaultDescription
localestringbrowser language (en or tr)Built-in languages: "en", "tr". For another language, pass it together with messages.
messagesPartial<Messages>—Overrides or adds translations. Missing keys fall back to English.
theme"light" \| "dark" \| "auto""auto"auto follows the operating system
skin"classic" \| "macos""classic"macos: a Finder-like look (blue folders, traffic lights, zebra rows, frosted menus). Works in light and dark mode.
density"comfortable" \| "compact""comfortable"
settingsbooleantrueA settings menu in the header for theme, view and density. The user's choice is stored under persistKey and wins over the props; if a prop changes later, the prop applies.
heightnumber \| string"100%"
className, style—Added to the root element (.tf-root)
thumbnailsbooleantrueShows thumbnails for images

Initial state#

PropTypeDefaultDescription
initialFolderstringlast folder, else root of the first volumeId of the folder to open first
rememberFolderbooleantrueReopen the folder the user was last in (stored under persistKey)
defaultViewPartial<Prefs>—Initial preferences. Whatever the user changes is stored under persistKey and takes precedence.
persistKeystring \| false"thefinder"localStorage key for preferences. false stores nothing.

Prefs:

FieldTypeDefault
view"grid" \| "list""grid"
sortKey"name" \| "size" \| "mtime" \| "kind""name"
sortDir1 \| -11
foldersFirstbooleantrue
detailsOpenbooleanfalse
sidebarWidthnumber232

Events#

PropTypeDescription
onOpen(entry) => boolean \| voidCalled when a file is opened (double click, Enter). Return true to skip the default behavior (preview, editor, download).
onChange(event) => voidCalled after every change
editorsCustomEditor[]Adds editors to the "Open with" menu. See Custom editors.

The onChange event: { type, entries?, ids? }. entries are new or updated items, ids are the ids of removed items.

typeWhen
uploadA file was uploaded
mkdir, mkfileA folder / file was created
renameRenamed
rmDeleted or moved to the trash
restoreBrought back from the trash or version history (the file was recreated)
revertA file was reverted to an older version
duplicateDuplicated
copy, movePasted or moved with drag & drop
archive, extractA zip was created / extracted
optimizeFiles were written by bulk image processing

Picker mode#

When onSelect is given the component acts as a picker: a "Select" button appears in the bottom bar and double-clicking a file selects it. For a modal picker, useFilePicker is more convenient.

PropTypeDescription
onSelect(entries: Entry[]) => voidCalled with the selected files
selectLabelstringButton text
onCancel() => voidWhen given, a "Cancel" button appears
multiplebooleanAllow selecting several files (default false)
acceptAcceptWhich files can be selected. The rest stay visible but can't be selected.
type Accept = string | string[] | ((entry: Entry) => boolean);
// "image/*"            a MIME group
// ".pdf,.docx"         extensions
// ["image/png", ".svg"]
// (e) => e.size < 2_000_000

matchesAccept(entry, accept) is exported so you can apply the same rule in your own code. Folders never match.


File picker#

useFilePicker(options)#

const picker = useFilePicker({ endpoint: "/api/files", accept: "image/*" });

<button disabled={picker.isOpen} onClick={async () => {
  const files = await picker.open();
  if (files) setCover(files[0].url);
}}>Choose image</button>

Returns { open(overrides?), isOpen }.

openFilePicker(options)#

The same picker outside React (vanilla JS, Vue, rich text editors). It appends a <dialog> to the end of <body> and removes it on close. React and React DOM must be bundled on the page.

import { openFilePicker } from "@thefinder/react";
import "@thefinder/react/styles.css";

const files = await openFilePicker({ endpoint: "/api/files", accept: ".pdf" });
if (files) input.value = files[0].url;

On the server (no document) it resolves null right away.

FilePickerOptions#

All <TheFinder /> props (except onSelect, onCancel, height, style), plus:

OptionDefaultDescription
absoluteUrlsfalseThe url field contains a full URL (https://site/uploads/a.png)
persistKey"thefinder-picker"The picker's view preferences are stored separately from the main file manager
popupfalsetrue (or { width, height, title }) opens the picker in its own browser window instead of a dialog, like elFinder. See Own window.

The picker closes with Escape, a click on the backdrop or the "Cancel" button. Escape first closes any open menu or dialog. In a popup window it closes with the "Cancel" button or the window's close button.

PickedFile#

Entry plus url: string. If the volume has a url, that address is used ("/uploads/a.png"); otherwise the API's file URL. pickedUrl(entry, client, absolute?) is exported to build the same URL in your own code.

Own window#

Like elFinder, theFinder can run in a separate browser window. Nothing has to be served for it: the window is an empty same-origin page that the opening page renders into with its own code and stylesheets, so every prop (custom editors, headers, callbacks) works as usual.

// The picker in a window: same promise, same result.
const files = await openFilePicker({ endpoint: "/api/files", accept: "image/*", popup: true });
const picker = useFilePicker({ endpoint: "/api/files", popup: { width: 960, height: 640 } });

// The whole file manager in a window.
const finder = openFinderWindow({ endpoint: "/api/files" }, { title: "Files" });
await finder?.closed; // finder.window, finder.close()

openFinderWindow(props, options?) takes <TheFinder /> props (except height) and { width = 1120, height = 720, title = "theFinder" }. It returns { window, close, closed }, or null when the browser blocks the popup.


Custom editors#

The editors prop adds an item to the "Open with" menu for matching files. The editor opens full-size inside the file manager.

import type { CustomEditor } from "@thefinder/react";

const csvViewer: CustomEditor = {
  id: "csv",
  label: "Open as table",
  match: (entry) => entry.name.toLowerCase().endsWith(".csv"),
  render: ({ entry, store, onClose }) => <CsvTable url={store.fileUrl(entry)} title={entry.name} onClose={onClose} />,
};

<TheFinder endpoint="/api/files" editors={[csvViewer]} />
FieldDescription
idA unique id
labelName shown in the menu
match(entry)Whether the editor is offered for this file
render({ entry, store, onClose })The editor content. onClose closes the editor.

Useful store members inside an editor:

MemberDescription
store.clientThe connected TheFinderClient: getContent, putContent, putBlob…
store.fileUrl(entry)The file's URL (the volume url or the API URL)
store.updateEntry(entry)Updates the size and date in the list after saving
store.toast(message, kind?)A notification; kind: "info" \| "success" \| "error"
store.fail(error)Reports an error to the user in their language
store.refresh()Reloads the current folder

Saving example:

const { entry: saved } = await store.client.putContent(entry.id, text);
store.updateEntry(saved);
store.toast("Saved", "success");

Store and hooks#

The component uses a small store (FinderStore) built on useSyncExternalStore. The store is reachable only from components rendered inside <TheFinder /> (for example custom editors).

ExportDescription
useFinder()The context: { store, t, locale, editors, pickMode, multiple, … }. Throws if called outside <TheFinder>.
useStore(selector)Subscribes to a slice of the store: const cwd = useStore((s) => s.cwd). The selector must return the same value for unchanged state.
FinderStoreThe store class. Read the state with store.getState() and watch changes with store.subscribe(fn).
FinderStateThe state type: cwd, selection, entries, volumes, uploads, clipboard, searchQuery…
FinderPrefsThe Prefs type

The store methods (open, select, upload, paste, remove…) were written for the UI itself. You can use them, but their signatures may change during 0.x.

i18n#

ExportDescription
locales{ en, tr }: the built-in message bundles
Messages, MessageKeyThe message type and its keys
createTranslator(locale, overrides?)(key, vars?) => string. Fills placeholders like {name} from vars.

To add a new language, translate every key and pass it in; anything left out shows in English:

import { locales, type Messages } from "@thefinder/react";

const de: Partial<Messages> = { upload: "Hochladen", newFolder: "Neuer Ordner" /* ... */ };
<TheFinder endpoint="/api/files" locale="de" messages={de} />

See the locales.en object for the full list of keys.

Icons#

The UI's hand-drawn SVG icons can be used in your own components too:

ComponentProps
<FileIcon entry={…} size={48} />A colored icon based on the file type (entry only needs kind, name, mime)
<FolderIcon size={48} open={false} />A folder
<Icon name="upload" size={16} />UI icons; accepts other SVG props too

Theming#

All styles live in @layer thefinder. Any rule you write outside the layer wins, no !important needed. The root element is .tf-root. It carries the data-theme (light, dark, auto), data-skin (classic, macos) and data-density attributes.

.tf-root {
  --tf-accent: #0f766e;
  --tf-radius: 4px;
  --tf-font: "Inter", system-ui, sans-serif;
}
.tf-root[data-theme="dark"] {
  --tf-bg: #101418;
}
VariableDescription
--tf-font, --tf-monoFonts
--tf-accentAccent color (selection, primary button, focus)
--tf-accent-textText on an accent fill
--tf-accent-inkAccent text on a neutral background (links); kept separate for contrast
--tf-accent-soft, --tf-accent-lineLight fill and line tones of the accent (derived from --tf-accent by default)
--tf-bg, --tf-surface, --tf-chrome, --tf-fieldBackground, panels, top/bottom bars, input fields
--tf-line, --tf-line-strongLines
--tf-text, --tf-text-2, --tf-text-3Text, secondary text, muted text
--tf-hover, --tf-press, --tf-sel, --tf-sel-mutedHover, press, selection, unfocused selection
--tf-danger, --tf-success, --tf-warnStatus colors
--tf-folder-back, --tf-folder-front, --tf-page, --tf-page-lineFolder and file icons
--tf-code-bg, --tf-code-activeCode editor background and active line
--tf-toast-bg, --tf-toast-textNotifications
--tf-backdrop, --tf-shadow-pop, --tf-shadow-dialogModal backdrop and shadows
--tf-radius, --tf-radius-lgCorner radius
--tf-header-h, --tf-toolbar-h, --tf-status-h, --tf-details-wBar heights and details panel width
--tk-*Code editor syntax colors (--tk-kw, --tk-str, --tk-com, --tk-num, --tk-fn, --tk-type, --tk-prop…)

Every class is prefixed with tf-. Classes other than the variables are internal structure and may change in minor releases.

Re-exports#

createClient, TheFinderClient, ApiError, encodeId and the Entry, VolumeInfo types come straight from @thefinder/core/client.

This page is generated from docs/api/react.md.