v2.0.0

<bmx-file-manager>

A file manager: a folder tree, the current folder in details or tiles, a path to click along, a preview, and every operation a person expects - new folder, rename in place, delete (asked first), cut, copy and paste, drag and drop between folders, upload by picker or by dropping files from the desktop (with bmx-upload's queue and progress), download, search.

21 properties · 6 events · 11 methods · 5 parts

Example

Double-click a folder to open it; drag files onto a folder, the tree or the path to move them (Ctrl to copy); drop files from your desktop to upload them; F2 renames; right-click for the menu. Anything in Archive is protected by the page and cannot be deleted.

Browse only, as tiles

With readonly nothing can change: the reader browses, previews and downloads.

Show markup
<bmx-file-manager id="ex-fm" label="Project files" root-label="My files"></bmx-file-manager>
<p class="note" id="ex-fm-out" role="status">Double-click a folder to open it; drag files onto a folder, the tree or the path to move them (Ctrl to copy); drop files from your desktop to upload them; F2 renames; right-click for the menu. Anything in Archive is protected by the page and cannot be deleted.</p>

<h3>Browse only, as tiles</h3>

<p class="note">With <code>readonly</code> nothing can change: the reader browses, previews and downloads.</p>
<bmx-file-manager id="ex-fm-ro" readonly view="tiles" show-tree="false" label="Brand assets" style="--bmx-file-manager-height: 22rem"></bmx-file-manager>

<script type="module">
  await customElements.whenDefined('bmx-file-manager');
  const day = 86400000;
  const now = Date.UTC(2026, 9, 6, 9, 30);
  const picture = (hue, label) => {
    const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="320" height="200" viewBox="0 0 320 200"><rect width="320" height="200" fill="hsl(${hue} 70% 88%)"/><circle cx="250" cy="55" r="28" fill="hsl(${(hue + 40) % 360} 90% 65%)"/><path d="M0 200 L90 90 L160 160 L215 110 L320 200z" fill="hsl(${hue} 45% 45%)"/><text x="16" y="34" font-family="sans-serif" font-size="20" fill="hsl(${hue} 50% 25%)">${label}</text></svg>`;
    return { url: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`, blob: new Blob([svg], { type: 'image/svg+xml' }) };
  };
  const team = picture(200, 'Team day');
  const office = picture(30, 'New office');
  const launch = picture(280, 'Launch');
  const fm = document.getElementById('ex-fm');
  fm.files = [
    { path: '/Projects/Website/index.html', content: '<!doctype html>\n<title>Acme</title>\n<h1>Hello</h1>\n', modified: now - 2 * day },
    { path: '/Projects/Website/styles.css', content: 'body {\n  font-family: system-ui;\n  margin: 0;\n}\n', modified: now - 3 * day },
    { path: '/Projects/Website/site.json', content: '{\n  "name": "acme",\n  "pages": 12\n}\n', modified: now - 6 * day },
    { path: '/Projects/Report Q3/q3-report.pdf', size: 2_480_000, modified: now - 9 * day },
    { path: '/Projects/Report Q3/figures.xlsx', size: 184_320, modified: now - 10 * day },
    { path: '/Projects/Report Q3/notes.md', content: '# Q3 notes\n\n- Revenue up 12%\n- Two new hires\n', modified: now - 8 * day },
    { path: '/Photos/team-day.svg', content: team.blob, thumbnail: team.url, modified: now - 30 * day },
    { path: '/Photos/new-office.svg', content: office.blob, thumbnail: office.url, modified: now - 45 * day },
    { path: '/Photos/launch.svg', content: launch.blob, thumbnail: launch.url, modified: now - 4 * day },
    ...Array.from({ length: 24 }, (_, i) => ({ path: `/Photos/Event/photo-${i + 1}.jpg`, size: 1_200_000 + i * 37_000, modified: now - (60 + i) * day })),
    { path: '/Notes/readme.txt', content: 'Files in this folder are shared with the whole team.\n', modified: now - day },
    { path: '/Notes/meeting-2026-10-01.md', content: '## Agenda\n\n1. Launch\n2. Hiring\n', modified: now - 5 * day },
    { path: '/Archive/2025.zip', size: 48_300_000, modified: now - 280 * day },
    { path: '/Archive/old-site.zip', size: 12_900_000, modified: now - 400 * day },
    { path: '/.env', content: 'SECRET=hidden', modified: now - 2 * day },
    { path: '/budget.xlsx', size: 92_160, modified: now - 12 * day },
  ];
  const out = document.getElementById('ex-fm-out');
  fm.addEventListener('bmxFileManagerAction', e => {
    const { kind, paths } = e.detail;
    if (kind === 'delete' && paths.some(p => p.startsWith('/Archive'))) {
      e.preventDefault();
      out.textContent = 'The page refused: the Archive folder is protected.';
    }
  });
  fm.addEventListener('bmxFileManagerChange', e => (out.textContent = `${e.detail.kind}: ${[...e.detail.paths, e.detail.target ?? '', e.detail.name ?? ''].filter(Boolean).join(' → ')}`));
  fm.addEventListener('bmxFileManagerOpen', e => (out.textContent = `Opened ${e.detail.entry.path}`));

  const ro = document.getElementById('ex-fm-ro');
  ro.files = [
    { path: '/team-day.svg', content: team.blob, thumbnail: team.url },
    { path: '/new-office.svg', content: office.blob, thumbnail: office.url },
    { path: '/launch.svg', content: launch.blob, thumbnail: launch.url },
    { path: '/brand-guide.pdf', size: 3_400_000 },
    { path: '/logo-pack.zip', size: 8_200_000 },
    { path: '/colours.json', content: '{ "primary": "#1971c2", "accent": "#7048e8" }' },
  ];
</script>

Storage is the page's, three ways: url, a server of the page's own that speaks a small JSON convention (listed on url); files, entries the page already holds, changed in memory; or provider, any object with a list(path) method and whichever actions it supports - S3, a CMS, a database. Binarymission hosts nothing; every action can be refused by the page before it happens.

fm.files = [{ path: '/Reports/q3.pdf', size: 248000 }, { path: '/Photos/team.jpg' }];

Keyboard: the listing is one stop. Arrows move (in two directions in the tiles view), Home and End, Enter opens a folder or a file, Backspace or Alt+Up goes up, Alt+Left and Alt+Right go back and forward, Space selects, Shift extends, Ctrl+A selects all, F2 renames, Delete deletes, Ctrl+C, Ctrl+X and Ctrl+V copy, cut and paste, Ctrl+Shift+N makes a folder, F5 lists again, Shift+F10 or the menu key opens the menu, and typing a name's first letters jumps to it. The folder tree is a tree.

Properties

PropertyAttributeTypeDefaultDescription
accept accept string — File types the upload accepts (image/*,.pdf).
files files BmxMemoryFileEntry[] | string — Files the page holds, instead of a provider: [{ path, size, modified, type, content }], as a property or JSON.
foldersFirst folders-first boolean true Folders before files.
headers headers Record<string, string> | string — Headers sent to url with every request (a token, say), as a property or JSON.
label label string — What the file manager shows: its accessible name.
locale locale string — A BCP 47 locale for dates and sizes. Default: the page's lang.
maxFileSize max-file-size number — The largest file the upload accepts, in bytes.
path path string '/' The folder shown. Changes write back here.
provider property only BmxFileProvider — Where the files are: an object with list(path) and whichever of createFolder, rename, remove, move, copy, upload, read and search it supports. Stands over url and files.
readonly readonly boolean false Nothing can be changed: browse, preview and download only.
rootLabel root-label string — What the root is called in the path and the tree.
selectable selectable 'single' | 'multiple' 'multiple' single or multiple (default).
showHidden show-hidden boolean false Show hidden entries (marked hidden, or named with a leading dot).
showPreview show-preview boolean true Show the preview pane. Changes write back here.
showTree show-tree boolean true Show the folder tree.
sortBy sort-by BmxFileSortKey 'name' The column the listing is sorted by: name, modified, size or type. Changes write back here.
sortDescending sort-descending boolean false Sort from the highest down. Changes write back here.
strings strings Partial<BmxFileManagerStrings> | string — Replacements for the wording, as a property or JSON.
url url string — A server of the page's own, speaking this convention (with url = /api/files): GET /api/files?path=/a lists; GET .../content?path= reads; GET .../search?q=&path= searches; POST .../folder {parent,name}, .../rename {path,name}, .../delete {paths}, .../move {paths,target}, .../copy {paths,target}; POST .../upload?path= multipart, field file. A failure's JSON message is shown.
view view 'details' | 'tiles' 'details' details (default) or tiles. Changes write back here.
withCredentials with-credentials boolean false Send cookies to url on another origin.

Events

EventDetailDescription
bmxFileManagerAction BmxFileManagerActionDetail An action is about to happen. Cancelable: the page can refuse it, or do it its own way.
bmxFileManagerChange BmxFileManagerActionDetail An action was done.
bmxFileManagerError BmxFileManagerErrorDetail The provider failed.
bmxFileManagerNavigate BmxFileManagerNavigateDetail The folder shown is about to change. Cancelable.
bmxFileManagerOpen BmxFileManagerOpenDetail A file was opened. Cancelable: by default it is shown in the preview pane.
bmxFileManagerSelect BmxFileManagerSelectDetail The selection changed.

Methods

MethodSignatureDescription
copyItems copyItems(paths: string[], target: string) => Promise<void> Copies entries into a folder.
createFolder createFolder(name?: string) => Promise<void> Makes a folder in the folder shown (named, or "New folder" ready to be renamed).
deleteItems deleteItems(paths: string[]) => Promise<void> Deletes entries, without asking (the page asks, when it calls this).
downloadItem downloadItem(path: string) => Promise<void> Downloads a file.
getSelection getSelection() => Promise<BmxFileEntry[]> The selected entries.
moveItems moveItems(paths: string[], target: string) => Promise<void> Moves entries into a folder.
navigate navigate(path: string) => Promise<void> Shows a folder.
refresh refresh() => Promise<void> Lists the folder shown again.
renameItem renameItem(path: string, name: string) => Promise<void> Renames an entry of the folder shown.
select select(paths: string[]) => Promise<void> Selects entries of the folder shown, by path.
uploadItems uploadItems(files: File[] | FileList, target?: string) => Promise<void> Uploads files into a folder (the one shown, by default), with the upload panel's progress.

CSS shadow parts

PartDescription
list the listing.
preview the preview pane.
status the line saying how many items there are.
toolbar the buttons, path and search.
tree the folder tree.

CSS custom properties

PropertyDescription
--bmx-file-manager-folder The colour of folders.
--bmx-file-manager-height How tall the file manager is. Default: 32rem.
--bmx-file-manager-preview-width The width of the preview pane. Default: 16rem.
--bmx-file-manager-tree-width The width of the folder tree. Default: 13rem.