<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.
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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
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
| Event | Detail | Description |
|---|---|---|
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
| Method | Signature | Description |
|---|---|---|
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
| Part | Description |
|---|---|
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
| Property | Description |
|---|---|
--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. |