Pages Hard
File Manager Page
A file manager / document browser page with folder tree sidebar, file grid/list views, breadcrumb navigation, upload area, and context menu. No libraries.
Open in Lab
MCP
vanilla-js css
Targets: JS HTML
Code
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--fm-sidebar-w: 240px;
--fm-topbar-h: 56px;
--fm-blue: #3b82f6;
--fm-blue-light: #eff6ff;
--fm-blue-dark: #2563eb;
--fm-gray-50: #f9fafb;
--fm-gray-100: #f3f4f6;
--fm-gray-200: #e5e7eb;
--fm-gray-300: #d1d5db;
--fm-gray-400: #9ca3af;
--fm-gray-500: #6b7280;
--fm-gray-600: #4b5563;
--fm-gray-700: #374151;
--fm-gray-800: #1f2937;
--fm-gray-900: #111827;
--fm-red: #ef4444;
--fm-radius: 8px;
--fm-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
--fm-shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.12), 0 4px 10px rgba(0, 0, 0, 0.08);
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
sans-serif;
background: var(--fm-gray-50);
color: var(--fm-gray-800);
line-height: 1.5;
overflow: hidden;
height: 100vh;
}
button {
cursor: pointer;
border: none;
background: none;
font: inherit;
color: inherit;
}
input,
select {
font: inherit;
}
/* Top Bar */
.fm-topbar {
display: flex;
align-items: center;
justify-content: space-between;
height: var(--fm-topbar-h);
padding: 0 16px;
background: #fff;
border-bottom: 1px solid var(--fm-gray-200);
gap: 12px;
position: relative;
z-index: 10;
}
.fm-topbar-left {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.fm-sidebar-toggle {
display: none;
width: 36px;
height: 36px;
align-items: center;
justify-content: center;
border-radius: var(--fm-radius);
color: var(--fm-gray-600);
}
.fm-sidebar-toggle:hover {
background: var(--fm-gray-100);
}
.fm-title {
font-size: 18px;
font-weight: 700;
color: var(--fm-gray-900);
white-space: nowrap;
}
.fm-topbar-center {
flex: 1;
max-width: 480px;
}
.fm-search {
position: relative;
width: 100%;
}
.fm-search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: var(--fm-gray-400);
pointer-events: none;
}
.fm-search-input {
width: 100%;
height: 36px;
padding: 0 12px 0 36px;
border: 1px solid var(--fm-gray-200);
border-radius: 9999px;
background: var(--fm-gray-50);
font-size: 14px;
outline: none;
transition: border-color .15s, box-shadow .15s;
}
.fm-search-input:focus {
border-color: var(--fm-blue);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.fm-topbar-right {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.fm-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 14px;
border-radius: var(--fm-radius);
font-size: 13px;
font-weight: 500;
border: 1px solid var(--fm-gray-200);
background: #fff;
color: var(--fm-gray-700);
transition: background .15s, border-color .15s;
white-space: nowrap;
}
.fm-btn:hover {
background: var(--fm-gray-50);
border-color: var(--fm-gray-300);
}
.fm-btn-primary {
background: var(--fm-blue);
color: #fff;
border-color: var(--fm-blue);
}
.fm-btn-primary:hover {
background: var(--fm-blue-dark);
border-color: var(--fm-blue-dark);
}
.fm-view-toggle {
display: flex;
border: 1px solid var(--fm-gray-200);
border-radius: var(--fm-radius);
overflow: hidden;
}
.fm-view-btn {
width: 36px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
color: var(--fm-gray-400);
transition: background .15s, color .15s;
}
.fm-view-btn:hover {
background: var(--fm-gray-50);
}
.fm-view-btn.active {
background: var(--fm-blue-light);
color: var(--fm-blue);
}
.fm-view-btn + .fm-view-btn {
border-left: 1px solid var(--fm-gray-200);
}
/* Body Layout */
.fm-body {
display: flex;
height: calc(100vh - var(--fm-topbar-h));
}
/* Sidebar */
.fm-sidebar {
width: var(--fm-sidebar-w);
flex-shrink: 0;
background: #fff;
border-right: 1px solid var(--fm-gray-200);
display: flex;
flex-direction: column;
overflow-y: auto;
padding: 16px 0;
}
.fm-storage {
padding: 0 16px 16px;
border-bottom: 1px solid var(--fm-gray-200);
margin-bottom: 8px;
}
.fm-storage-info {
display: flex;
justify-content: space-between;
font-size: 12px;
margin-bottom: 8px;
}
.fm-storage-label {
font-weight: 600;
color: var(--fm-gray-700);
}
.fm-storage-value {
color: var(--fm-gray-500);
}
.fm-storage-bar {
height: 6px;
background: var(--fm-gray-200);
border-radius: 9999px;
overflow: hidden;
}
.fm-storage-fill {
height: 100%;
background: var(--fm-blue);
border-radius: 9999px;
transition: width .3s;
}
/* Folder Tree */
.fm-tree {
padding: 0 8px;
}
.fm-tree-list {
list-style: none;
}
.fm-tree-list .fm-tree-list {
padding-left: 16px;
}
.fm-tree-item > .fm-tree-list {
overflow: hidden;
}
.fm-tree-item.collapsed > .fm-tree-list {
display: none;
}
.fm-tree-row {
display: flex;
align-items: center;
gap: 4px;
padding: 5px 8px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
color: var(--fm-gray-700);
transition: background .12s;
user-select: none;
}
.fm-tree-row:hover {
background: var(--fm-gray-100);
}
.fm-tree-row.active {
background: var(--fm-blue-light);
color: var(--fm-blue);
font-weight: 500;
}
.fm-tree-arrow {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--fm-gray-400);
transition: transform .15s;
padding: 0;
}
.fm-tree-arrow.expanded {
transform: rotate(90deg);
}
.fm-tree-arrow-spacer {
width: 20px;
flex-shrink: 0;
}
.fm-tree-icon {
flex-shrink: 0;
width: 18px;
height: 18px;
}
.fm-tree-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Main Content */
.fm-main {
flex: 1;
overflow-y: auto;
padding: 20px 24px;
}
/* Breadcrumb */
.fm-breadcrumb {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 16px;
font-size: 13px;
}
.fm-breadcrumb-item {
padding: 2px 6px;
border-radius: 4px;
color: var(--fm-gray-500);
transition: color .15s, background .15s;
}
button.fm-breadcrumb-item:hover {
color: var(--fm-blue);
background: var(--fm-blue-light);
}
.fm-breadcrumb-item.current {
color: var(--fm-gray-800);
font-weight: 500;
}
.fm-breadcrumb-sep {
color: var(--fm-gray-300);
display: flex;
}
/* Toolbar */
.fm-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
gap: 12px;
}
.fm-select-all {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--fm-gray-500);
cursor: pointer;
}
.fm-select-all-check {
width: 16px;
height: 16px;
accent-color: var(--fm-blue);
cursor: pointer;
}
.fm-sort {
display: flex;
align-items: center;
gap: 6px;
}
.fm-sort-label {
font-size: 13px;
color: var(--fm-gray-500);
}
.fm-sort-select {
padding: 4px 8px;
border: 1px solid var(--fm-gray-200);
border-radius: 6px;
font-size: 13px;
color: var(--fm-gray-700);
background: #fff;
outline: none;
cursor: pointer;
}
.fm-sort-select:focus {
border-color: var(--fm-blue);
}
/* Grid View */
.fm-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 12px;
}
.fm-grid .fm-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 12px 14px;
background: #fff;
border: 2px solid transparent;
border-radius: var(--fm-radius);
cursor: pointer;
transition: border-color .15s, background .15s, box-shadow .15s;
position: relative;
}
.fm-grid .fm-item:hover {
background: var(--fm-gray-50);
box-shadow: var(--fm-shadow);
}
.fm-grid .fm-item.selected {
border-color: var(--fm-blue);
background: var(--fm-blue-light);
}
.fm-grid .fm-item-check {
position: absolute;
top: 8px;
left: 8px;
opacity: 0;
transition: opacity .12s;
}
.fm-grid .fm-item:hover .fm-item-check,
.fm-grid .fm-item.selected .fm-item-check {
opacity: 1;
}
.fm-item-check input {
width: 16px;
height: 16px;
accent-color: var(--fm-blue);
cursor: pointer;
}
.fm-grid .fm-item-icon {
margin-bottom: 10px;
}
.fm-grid .fm-item-info {
text-align: center;
width: 100%;
}
.fm-grid .fm-item-name {
display: block;
font-size: 13px;
font-weight: 500;
color: var(--fm-gray-800);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fm-grid .fm-item-meta {
display: block;
font-size: 11px;
color: var(--fm-gray-400);
margin-top: 2px;
}
.fm-grid .fm-item-size,
.fm-grid .fm-item-date,
.fm-grid .fm-item-type-col {
display: none;
}
/* List View */
.fm-list {
display: flex;
flex-direction: column;
gap: 2px;
}
.fm-list .fm-item {
display: grid;
grid-template-columns: 32px 36px 1fr 80px 120px 90px;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #fff;
border: 2px solid transparent;
border-radius: 6px;
cursor: pointer;
transition: border-color .15s, background .15s;
}
.fm-list .fm-item:hover {
background: var(--fm-gray-50);
}
.fm-list .fm-item.selected {
border-color: var(--fm-blue);
background: var(--fm-blue-light);
}
.fm-list .fm-item-check {
display: flex;
align-items: center;
justify-content: center;
}
.fm-list .fm-item-icon {
display: flex;
align-items: center;
justify-content: center;
}
.fm-list .fm-item-icon svg {
width: 28px;
height: 28px;
}
.fm-list .fm-item-info {
overflow: hidden;
}
.fm-list .fm-item-name {
font-size: 13px;
font-weight: 500;
color: var(--fm-gray-800);
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fm-list .fm-item-meta {
display: none;
}
.fm-list .fm-item-size,
.fm-list .fm-item-date,
.fm-list .fm-item-type-col {
font-size: 12px;
color: var(--fm-gray-500);
white-space: nowrap;
}
/* Upload Overlay */
.fm-upload-overlay {
position: fixed;
inset: 0;
background: rgba(59, 130, 246, 0.08);
z-index: 100;
display: none;
align-items: center;
justify-content: center;
pointer-events: none;
}
.fm-upload-overlay.visible {
display: flex;
}
.fm-upload-overlay-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 48px;
border: 3px dashed var(--fm-blue);
border-radius: 16px;
background: rgba(255, 255, 255, 0.95);
}
.fm-upload-overlay-content p {
font-size: 16px;
font-weight: 600;
color: var(--fm-blue);
}
/* Context Menu */
.fm-context-menu {
position: fixed;
z-index: 200;
min-width: 180px;
background: #fff;
border: 1px solid var(--fm-gray-200);
border-radius: 10px;
padding: 6px;
box-shadow: var(--fm-shadow-lg);
display: none;
}
.fm-context-menu.visible {
display: block;
}
.fm-context-item {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 8px 12px;
font-size: 13px;
border-radius: 6px;
color: var(--fm-gray-700);
transition: background .1s;
text-align: left;
}
.fm-context-item:hover {
background: var(--fm-gray-100);
}
.fm-context-danger {
color: var(--fm-red);
}
.fm-context-danger:hover {
background: #fef2f2;
}
.fm-context-divider {
height: 1px;
background: var(--fm-gray-200);
margin: 4px 0;
}
/* Rename Modal */
.fm-modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 300;
display: none;
align-items: center;
justify-content: center;
}
.fm-modal-backdrop.visible {
display: flex;
}
.fm-modal {
background: #fff;
border-radius: 12px;
padding: 24px;
width: 400px;
max-width: 90vw;
box-shadow: var(--fm-shadow-lg);
}
.fm-modal-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
color: var(--fm-gray-900);
}
.fm-modal-input {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--fm-gray-300);
border-radius: var(--fm-radius);
font-size: 14px;
outline: none;
margin-bottom: 20px;
}
.fm-modal-input:focus {
border-color: var(--fm-blue);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.fm-modal-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
/* Fade-out animation for deleted items */
.fm-item.removing {
opacity: 0;
transform: scale(0.95);
transition: opacity .25s, transform .25s;
}
/* Responsive */
@media (max-width: 768px) {
.fm-sidebar-toggle {
display: flex;
}
.fm-sidebar {
position: fixed;
top: var(--fm-topbar-h);
left: 0;
bottom: 0;
z-index: 50;
transform: translateX(-100%);
transition: transform .25s ease;
box-shadow: var(--fm-shadow-lg);
}
.fm-sidebar.open {
transform: translateX(0);
}
.fm-topbar-center {
display: none;
}
.fm-new-folder-btn span,
.fm-upload-btn span {
display: none;
}
.fm-grid {
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
}
.fm-list .fm-item {
grid-template-columns: 28px 28px 1fr 60px;
}
.fm-list .fm-item-date,
.fm-list .fm-item-type-col {
display: none;
}
}(function () {
"use strict";
/* ── State ── */
const fileSystem = {
"My Files": {
type: "folder",
children: {
Documents: {
type: "folder",
children: {
Projects: { type: "folder", children: {} },
Reports: { type: "folder", children: {} },
},
},
Images: { type: "folder", children: {} },
Videos: { type: "folder", children: {} },
},
},
"Shared with me": { type: "folder", children: {} },
Starred: { type: "folder", children: {} },
Trash: { type: "folder", children: {} },
};
let currentPath = ["My Files", "Documents"];
let currentView = "grid";
let selectedItems = new Set();
let contextTarget = null;
let lastClickedIndex = -1;
/* ── DOM refs ── */
const content = document.getElementById("fm-content");
const contextMenu = document.getElementById("fm-context-menu");
const uploadOverlay = document.getElementById("fm-upload-overlay");
const fileInput = document.getElementById("fm-file-input");
const renameModal = document.getElementById("fm-rename-modal");
const renameInput = document.getElementById("fm-rename-input");
const selectAllCheck = document.querySelector(".fm-select-all-check");
const itemCountEl = document.querySelector(".fm-item-count");
const sortSelect = document.getElementById("fm-sort-select");
const breadcrumbEl = document.querySelector(".fm-breadcrumb");
const sidebar = document.querySelector(".fm-sidebar");
const sidebarToggle = document.querySelector(".fm-sidebar-toggle");
/* ── View Toggle ── */
document.querySelectorAll(".fm-view-btn").forEach(function (btn) {
btn.addEventListener("click", function () {
document.querySelectorAll(".fm-view-btn").forEach(function (b) {
b.classList.remove("active");
});
btn.classList.add("active");
currentView = btn.dataset.view;
content.classList.remove("fm-grid", "fm-list");
content.classList.add(currentView === "grid" ? "fm-grid" : "fm-list");
});
});
/* ── Sidebar Toggle (Mobile) ── */
sidebarToggle.addEventListener("click", function () {
sidebar.classList.toggle("open");
});
/* ── Folder Tree ── */
document.querySelectorAll(".fm-tree-item").forEach(function (item) {
var row = item.querySelector(":scope > .fm-tree-row");
var arrow = row.querySelector(".fm-tree-arrow");
var children = item.querySelector(":scope > .fm-tree-list");
row.addEventListener("click", function (e) {
e.stopPropagation();
// Toggle expand/collapse if has children
if (arrow && children) {
if (e.target.closest(".fm-tree-arrow")) {
arrow.classList.toggle("expanded");
item.classList.toggle("collapsed");
return;
}
// Clicking the row also expands
if (item.classList.contains("collapsed")) {
arrow.classList.add("expanded");
item.classList.remove("collapsed");
}
}
// Navigate to folder
var path = item.dataset.path;
if (path) {
currentPath = path.split("/");
updateBreadcrumb();
updateTreeActive(path);
sidebar.classList.remove("open");
}
});
});
function updateTreeActive(path) {
document.querySelectorAll(".fm-tree-row").forEach(function (r) {
r.classList.remove("active");
});
var target = document.querySelector('.fm-tree-item[data-path="' + path + '"] > .fm-tree-row');
if (target) target.classList.add("active");
}
/* ── Breadcrumb ── */
function updateBreadcrumb() {
breadcrumbEl.innerHTML = "";
currentPath.forEach(function (segment, i) {
if (i > 0) {
var sep = document.createElement("span");
sep.className = "fm-breadcrumb-sep";
sep.innerHTML =
'<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4,2 8,6 4,10"/></svg>';
breadcrumbEl.appendChild(sep);
}
var isLast = i === currentPath.length - 1;
var el = document.createElement(isLast ? "span" : "button");
el.className = "fm-breadcrumb-item" + (isLast ? " current" : "");
el.textContent = segment;
if (!isLast) {
el.dataset.path = currentPath.slice(0, i + 1).join("/");
el.addEventListener("click", function () {
currentPath = this.dataset.path.split("/");
updateBreadcrumb();
updateTreeActive(currentPath.join("/"));
});
}
breadcrumbEl.appendChild(el);
});
}
/* ── Selection ── */
function getItems() {
return Array.from(content.querySelectorAll(".fm-item"));
}
content.addEventListener("click", function (e) {
var item = e.target.closest(".fm-item");
if (!item) return;
// Ignore checkbox direct clicks — handled below
if (e.target.tagName === "INPUT") return;
var items = getItems();
var index = items.indexOf(item);
if (e.shiftKey && lastClickedIndex >= 0) {
var start = Math.min(lastClickedIndex, index);
var end = Math.max(lastClickedIndex, index);
for (var i = start; i <= end; i++) {
selectItem(items[i], true);
}
} else if (e.ctrlKey || e.metaKey) {
toggleSelect(item);
} else {
clearSelection();
selectItem(item, true);
}
lastClickedIndex = index;
syncSelectAll();
});
content.addEventListener("change", function (e) {
if (e.target.type === "checkbox") {
var item = e.target.closest(".fm-item");
if (item) {
selectItem(item, e.target.checked);
syncSelectAll();
}
}
});
selectAllCheck.addEventListener("change", function () {
var items = getItems();
var checked = selectAllCheck.checked;
items.forEach(function (item) {
selectItem(item, checked);
});
});
function selectItem(item, selected) {
var name = item.dataset.name;
var check = item.querySelector('input[type="checkbox"]');
if (selected) {
item.classList.add("selected");
selectedItems.add(name);
if (check) check.checked = true;
} else {
item.classList.remove("selected");
selectedItems.delete(name);
if (check) check.checked = false;
}
}
function toggleSelect(item) {
selectItem(item, !item.classList.contains("selected"));
}
function clearSelection() {
getItems().forEach(function (item) {
selectItem(item, false);
});
selectedItems.clear();
}
function syncSelectAll() {
var items = getItems();
var allChecked =
items.length > 0 &&
items.every(function (i) {
return i.classList.contains("selected");
});
selectAllCheck.checked = allChecked;
selectAllCheck.indeterminate = !allChecked && selectedItems.size > 0;
itemCountEl.textContent =
selectedItems.size > 0 ? selectedItems.size + " selected" : items.length + " items";
}
/* ── Double-click folder ── */
content.addEventListener("dblclick", function (e) {
var item = e.target.closest(".fm-item");
if (!item || item.dataset.type !== "folder") return;
currentPath.push(item.dataset.name);
updateBreadcrumb();
updateTreeActive(currentPath.join("/"));
});
/* ── Context Menu ── */
content.addEventListener("contextmenu", function (e) {
var item = e.target.closest(".fm-item");
if (!item) return;
e.preventDefault();
contextTarget = item;
// Position
var x = e.clientX;
var y = e.clientY;
contextMenu.style.left = x + "px";
contextMenu.style.top = y + "px";
contextMenu.classList.add("visible");
// Adjust if off-screen
requestAnimationFrame(function () {
var rect = contextMenu.getBoundingClientRect();
if (rect.right > window.innerWidth) {
contextMenu.style.left = x - rect.width + "px";
}
if (rect.bottom > window.innerHeight) {
contextMenu.style.top = y - rect.height + "px";
}
});
});
document.addEventListener("click", function () {
contextMenu.classList.remove("visible");
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") {
contextMenu.classList.remove("visible");
renameModal.classList.remove("visible");
}
});
contextMenu.addEventListener("click", function (e) {
var btn = e.target.closest(".fm-context-item");
if (!btn || !contextTarget) return;
var action = btn.dataset.action;
switch (action) {
case "open":
if (contextTarget.dataset.type === "folder") {
currentPath.push(contextTarget.dataset.name);
updateBreadcrumb();
updateTreeActive(currentPath.join("/"));
}
break;
case "rename":
renameInput.value = contextTarget.dataset.name;
renameModal.classList.add("visible");
setTimeout(function () {
renameInput.focus();
var dotIndex = renameInput.value.lastIndexOf(".");
renameInput.setSelectionRange(0, dotIndex > 0 ? dotIndex : renameInput.value.length);
}, 50);
break;
case "delete":
if (confirm('Delete "' + contextTarget.dataset.name + '"?')) {
contextTarget.classList.add("removing");
var target = contextTarget;
setTimeout(function () {
target.remove();
syncSelectAll();
}, 250);
}
break;
case "download":
// Simulate download
var link = document.createElement("a");
link.href = "#";
link.download = contextTarget.dataset.name;
break;
case "copy":
case "move":
// Visual feedback only
break;
}
contextMenu.classList.remove("visible");
});
/* ── Rename Modal ── */
renameModal.querySelector(".fm-modal-cancel").addEventListener("click", function () {
renameModal.classList.remove("visible");
});
renameModal.querySelector(".fm-modal-confirm").addEventListener("click", doRename);
renameInput.addEventListener("keydown", function (e) {
if (e.key === "Enter") doRename();
});
function doRename() {
var newName = renameInput.value.trim();
if (!newName || !contextTarget) return;
contextTarget.dataset.name = newName;
contextTarget.querySelector(".fm-item-name").textContent = newName;
renameModal.classList.remove("visible");
}
renameModal.addEventListener("click", function (e) {
if (e.target === renameModal) renameModal.classList.remove("visible");
});
/* ── Upload Drag & Drop ── */
var dragCounter = 0;
document.addEventListener("dragenter", function (e) {
e.preventDefault();
dragCounter++;
if (dragCounter === 1) uploadOverlay.classList.add("visible");
});
document.addEventListener("dragleave", function (e) {
e.preventDefault();
dragCounter--;
if (dragCounter <= 0) {
dragCounter = 0;
uploadOverlay.classList.remove("visible");
}
});
document.addEventListener("dragover", function (e) {
e.preventDefault();
});
document.addEventListener("drop", function (e) {
e.preventDefault();
dragCounter = 0;
uploadOverlay.classList.remove("visible");
// Files would be in e.dataTransfer.files
});
/* ── Upload Button ── */
document.querySelector(".fm-upload-btn").addEventListener("click", function () {
fileInput.click();
});
fileInput.addEventListener("change", function () {
// Files added — no-op for demo
fileInput.value = "";
});
/* ── New Folder ── */
document.querySelector(".fm-new-folder-btn").addEventListener("click", function () {
var name = prompt("New folder name:");
if (!name || !name.trim()) return;
name = name.trim();
var html =
'<div class="fm-item-check"><input type="checkbox" /></div>' +
'<div class="fm-item-icon fm-icon-folder">' +
'<svg width="48" height="48" viewBox="0 0 48 48" fill="#3b82f6"><path d="M4 12h16l4-4h20v32H4z"/></svg>' +
"</div>" +
'<div class="fm-item-info">' +
'<span class="fm-item-name">' +
name +
"</span>" +
'<span class="fm-item-meta">0 items</span>' +
"</div>" +
'<span class="fm-item-size">\u2014</span>' +
'<span class="fm-item-date">Just now</span>' +
'<span class="fm-item-type-col">Folder</span>';
var el = document.createElement("div");
el.className = "fm-item";
el.dataset.name = name;
el.dataset.type = "folder";
el.dataset.size = "0";
el.dataset.date = new Date().toISOString().slice(0, 10);
el.dataset.items = "0 items";
el.innerHTML = html;
// Insert before first non-folder or at end
var items = getItems();
var firstFile = items.find(function (i) {
return i.dataset.type !== "folder";
});
if (firstFile) {
content.insertBefore(el, firstFile);
} else {
content.appendChild(el);
}
syncSelectAll();
});
/* ── Sort ── */
sortSelect.addEventListener("change", function () {
var key = sortSelect.value;
var items = getItems();
var folders = items.filter(function (i) {
return i.dataset.type === "folder";
});
var files = items.filter(function (i) {
return i.dataset.type !== "folder";
});
var comparator;
switch (key) {
case "name":
comparator = function (a, b) {
return a.dataset.name.localeCompare(b.dataset.name);
};
break;
case "size":
comparator = function (a, b) {
return parseInt(a.dataset.size || "0") - parseInt(b.dataset.size || "0");
};
break;
case "date":
comparator = function (a, b) {
return (b.dataset.date || "").localeCompare(a.dataset.date || "");
};
break;
case "type":
comparator = function (a, b) {
return (a.dataset.type || "").localeCompare(b.dataset.type || "");
};
break;
default:
comparator = function () {
return 0;
};
}
folders.sort(comparator);
files.sort(comparator);
folders.concat(files).forEach(function (item) {
content.appendChild(item);
});
});
/* ── Search ── */
document.querySelector(".fm-search-input").addEventListener("input", function (e) {
var query = e.target.value.toLowerCase();
getItems().forEach(function (item) {
var name = (item.dataset.name || "").toLowerCase();
item.style.display = name.includes(query) ? "" : "none";
});
});
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>File Manager</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="fm">
<!-- Top Bar -->
<header class="fm-topbar">
<div class="fm-topbar-left">
<button class="fm-sidebar-toggle" aria-label="Toggle sidebar">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
<line x1="3" y1="5" x2="17" y2="5"/><line x1="3" y1="10" x2="17" y2="10"/><line x1="3" y1="15" x2="17" y2="15"/>
</svg>
</button>
<h1 class="fm-title">File Manager</h1>
</div>
<div class="fm-topbar-center">
<div class="fm-search">
<svg class="fm-search-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="7" cy="7" r="5"/><line x1="11" y1="11" x2="15" y2="15"/>
</svg>
<input type="text" class="fm-search-input" placeholder="Search files and folders..." />
</div>
</div>
<div class="fm-topbar-right">
<button class="fm-btn fm-btn-primary fm-upload-btn">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
<line x1="8" y1="2" x2="8" y2="11"/><polyline points="4,6 8,2 12,6"/><line x1="2" y1="14" x2="14" y2="14"/>
</svg>
Upload
</button>
<button class="fm-btn fm-new-folder-btn">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
<path d="M2 4h4l2-2h6v11H2z"/><line x1="8" y1="7" x2="8" y2="13"/><line x1="5" y1="10" x2="11" y2="10"/>
</svg>
New Folder
</button>
<div class="fm-view-toggle">
<button class="fm-view-btn active" data-view="grid" aria-label="Grid view">
<svg width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<rect x="1" y="1" width="7" height="7" rx="1"/><rect x="10" y="1" width="7" height="7" rx="1"/>
<rect x="1" y="10" width="7" height="7" rx="1"/><rect x="10" y="10" width="7" height="7" rx="1"/>
</svg>
</button>
<button class="fm-view-btn" data-view="list" aria-label="List view">
<svg width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<rect x="1" y="2" width="16" height="3" rx="1"/><rect x="1" y="7.5" width="16" height="3" rx="1"/>
<rect x="1" y="13" width="16" height="3" rx="1"/>
</svg>
</button>
</div>
</div>
</header>
<div class="fm-body">
<!-- Sidebar -->
<aside class="fm-sidebar">
<div class="fm-storage">
<div class="fm-storage-info">
<span class="fm-storage-label">Storage</span>
<span class="fm-storage-value">4.2 GB of 15 GB</span>
</div>
<div class="fm-storage-bar">
<div class="fm-storage-fill" style="width: 28%"></div>
</div>
</div>
<nav class="fm-tree" role="tree">
<ul class="fm-tree-list">
<li class="fm-tree-item" data-path="My Files" role="treeitem">
<div class="fm-tree-row active">
<button class="fm-tree-arrow expanded" aria-label="Toggle">
<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><polygon points="2,2 8,5 2,8"/></svg>
</button>
<svg class="fm-tree-icon" width="18" height="18" viewBox="0 0 18 18" fill="#3b82f6">
<path d="M2 4h5l2-2h7v13H2z"/>
</svg>
<span class="fm-tree-label">My Files</span>
</div>
<ul class="fm-tree-list">
<li class="fm-tree-item" data-path="My Files/Documents" role="treeitem">
<div class="fm-tree-row">
<button class="fm-tree-arrow expanded" aria-label="Toggle">
<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><polygon points="2,2 8,5 2,8"/></svg>
</button>
<svg class="fm-tree-icon" width="18" height="18" viewBox="0 0 18 18" fill="#3b82f6">
<path d="M2 4h5l2-2h7v13H2z"/>
</svg>
<span class="fm-tree-label">Documents</span>
</div>
<ul class="fm-tree-list">
<li class="fm-tree-item" data-path="My Files/Documents/Projects" role="treeitem">
<div class="fm-tree-row">
<span class="fm-tree-arrow-spacer"></span>
<svg class="fm-tree-icon" width="18" height="18" viewBox="0 0 18 18" fill="#3b82f6">
<path d="M2 4h5l2-2h7v13H2z"/>
</svg>
<span class="fm-tree-label">Projects</span>
</div>
</li>
<li class="fm-tree-item" data-path="My Files/Documents/Reports" role="treeitem">
<div class="fm-tree-row">
<span class="fm-tree-arrow-spacer"></span>
<svg class="fm-tree-icon" width="18" height="18" viewBox="0 0 18 18" fill="#3b82f6">
<path d="M2 4h5l2-2h7v13H2z"/>
</svg>
<span class="fm-tree-label">Reports</span>
</div>
</li>
</ul>
</li>
<li class="fm-tree-item" data-path="My Files/Images" role="treeitem">
<div class="fm-tree-row">
<span class="fm-tree-arrow-spacer"></span>
<svg class="fm-tree-icon" width="18" height="18" viewBox="0 0 18 18" fill="#3b82f6">
<path d="M2 4h5l2-2h7v13H2z"/>
</svg>
<span class="fm-tree-label">Images</span>
</div>
</li>
<li class="fm-tree-item" data-path="My Files/Videos" role="treeitem">
<div class="fm-tree-row">
<span class="fm-tree-arrow-spacer"></span>
<svg class="fm-tree-icon" width="18" height="18" viewBox="0 0 18 18" fill="#3b82f6">
<path d="M2 4h5l2-2h7v13H2z"/>
</svg>
<span class="fm-tree-label">Videos</span>
</div>
</li>
</ul>
</li>
<li class="fm-tree-item" data-path="Shared with me" role="treeitem">
<div class="fm-tree-row">
<span class="fm-tree-arrow-spacer"></span>
<svg class="fm-tree-icon" width="18" height="18" viewBox="0 0 18 18" fill="#8b5cf6">
<circle cx="6" cy="7" r="3"/><circle cx="12" cy="7" r="3"/><path d="M1 15c0-3 3-5 5-5s5 2 5 5"/>
</svg>
<span class="fm-tree-label">Shared with me</span>
</div>
</li>
<li class="fm-tree-item" data-path="Starred" role="treeitem">
<div class="fm-tree-row">
<span class="fm-tree-arrow-spacer"></span>
<svg class="fm-tree-icon" width="18" height="18" viewBox="0 0 18 18" fill="#eab308">
<polygon points="9,1 11.5,6.5 17,7 13,11 14,17 9,14 4,17 5,11 1,7 6.5,6.5"/>
</svg>
<span class="fm-tree-label">Starred</span>
</div>
</li>
<li class="fm-tree-item" data-path="Trash" role="treeitem">
<div class="fm-tree-row">
<span class="fm-tree-arrow-spacer"></span>
<svg class="fm-tree-icon" width="18" height="18" viewBox="0 0 18 18" fill="#6b7280">
<path d="M4 5h10l-1 11H5L4 5z"/><line x1="2" y1="5" x2="16" y2="5" stroke="#6b7280" stroke-width="1.5" fill="none"/>
<path d="M7 5V3h4v2" fill="none" stroke="#6b7280" stroke-width="1.5"/>
</svg>
<span class="fm-tree-label">Trash</span>
</div>
</li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="fm-main">
<!-- Breadcrumb -->
<div class="fm-breadcrumb">
<button class="fm-breadcrumb-item" data-path="My Files">My Files</button>
<span class="fm-breadcrumb-sep">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4,2 8,6 4,10"/></svg>
</span>
<span class="fm-breadcrumb-item current">Documents</span>
</div>
<!-- Toolbar -->
<div class="fm-toolbar">
<label class="fm-select-all">
<input type="checkbox" class="fm-select-all-check" />
<span class="fm-item-count">8 items</span>
</label>
<div class="fm-sort">
<label for="fm-sort-select" class="fm-sort-label">Sort by:</label>
<select id="fm-sort-select" class="fm-sort-select">
<option value="name">Name</option>
<option value="size">Size</option>
<option value="date">Date Modified</option>
<option value="type">Type</option>
</select>
</div>
</div>
<!-- File Grid -->
<div class="fm-content fm-grid" id="fm-content">
<!-- Folders -->
<div class="fm-item" data-name="Projects" data-type="folder" data-size="0" data-date="2026-03-18" data-items="5 items">
<div class="fm-item-check"><input type="checkbox" /></div>
<div class="fm-item-icon fm-icon-folder">
<svg width="48" height="48" viewBox="0 0 48 48" fill="#3b82f6">
<path d="M4 12h16l4-4h20v32H4z"/>
</svg>
</div>
<div class="fm-item-info">
<span class="fm-item-name">Projects</span>
<span class="fm-item-meta">5 items</span>
</div>
<span class="fm-item-size">—</span>
<span class="fm-item-date">Mar 18, 2026</span>
<span class="fm-item-type-col">Folder</span>
</div>
<div class="fm-item" data-name="Reports" data-type="folder" data-size="0" data-date="2026-03-15" data-items="3 items">
<div class="fm-item-check"><input type="checkbox" /></div>
<div class="fm-item-icon fm-icon-folder">
<svg width="48" height="48" viewBox="0 0 48 48" fill="#3b82f6">
<path d="M4 12h16l4-4h20v32H4z"/>
</svg>
</div>
<div class="fm-item-info">
<span class="fm-item-name">Reports</span>
<span class="fm-item-meta">3 items</span>
</div>
<span class="fm-item-size">—</span>
<span class="fm-item-date">Mar 15, 2026</span>
<span class="fm-item-type-col">Folder</span>
</div>
<!-- Files -->
<div class="fm-item" data-name="design-mockup.fig" data-type="design" data-size="2516582" data-date="2026-03-19">
<div class="fm-item-check"><input type="checkbox" /></div>
<div class="fm-item-icon fm-icon-design">
<svg width="48" height="48" viewBox="0 0 48 48">
<rect x="6" y="4" width="36" height="40" rx="4" fill="#a855f7"/>
<circle cx="24" cy="18" r="6" fill="white" opacity="0.9"/>
<rect x="16" y="28" width="16" height="8" rx="2" fill="white" opacity="0.9"/>
</svg>
</div>
<div class="fm-item-info">
<span class="fm-item-name">design-mockup.fig</span>
<span class="fm-item-meta">2.4 MB</span>
</div>
<span class="fm-item-size">2.4 MB</span>
<span class="fm-item-date">Mar 19, 2026</span>
<span class="fm-item-type-col">Design</span>
</div>
<div class="fm-item" data-name="quarterly-report.pdf" data-type="pdf" data-size="1887437" data-date="2026-03-17">
<div class="fm-item-check"><input type="checkbox" /></div>
<div class="fm-item-icon fm-icon-pdf">
<svg width="48" height="48" viewBox="0 0 48 48">
<rect x="6" y="4" width="36" height="40" rx="4" fill="#ef4444"/>
<text x="24" y="30" text-anchor="middle" fill="white" font-size="12" font-weight="700" font-family="system-ui">PDF</text>
</svg>
</div>
<div class="fm-item-info">
<span class="fm-item-name">quarterly-report.pdf</span>
<span class="fm-item-meta">1.8 MB</span>
</div>
<span class="fm-item-size">1.8 MB</span>
<span class="fm-item-date">Mar 17, 2026</span>
<span class="fm-item-type-col">PDF</span>
</div>
<div class="fm-item" data-name="presentation.pptx" data-type="doc" data-size="5452595" data-date="2026-03-16">
<div class="fm-item-check"><input type="checkbox" /></div>
<div class="fm-item-icon fm-icon-doc">
<svg width="48" height="48" viewBox="0 0 48 48">
<rect x="6" y="4" width="36" height="40" rx="4" fill="#f97316"/>
<rect x="14" y="14" width="20" height="3" rx="1" fill="white" opacity="0.9"/>
<rect x="14" y="21" width="16" height="3" rx="1" fill="white" opacity="0.9"/>
<rect x="14" y="28" width="20" height="3" rx="1" fill="white" opacity="0.9"/>
</svg>
</div>
<div class="fm-item-info">
<span class="fm-item-name">presentation.pptx</span>
<span class="fm-item-meta">5.2 MB</span>
</div>
<span class="fm-item-size">5.2 MB</span>
<span class="fm-item-date">Mar 16, 2026</span>
<span class="fm-item-type-col">Document</span>
</div>
<div class="fm-item" data-name="photo-001.jpg" data-type="image" data-size="3250586" data-date="2026-03-14">
<div class="fm-item-check"><input type="checkbox" /></div>
<div class="fm-item-icon fm-icon-image">
<svg width="48" height="48" viewBox="0 0 48 48">
<rect x="6" y="4" width="36" height="40" rx="4" fill="#22c55e"/>
<circle cx="19" cy="18" r="4" fill="white" opacity="0.9"/>
<polygon points="12,36 22,24 28,30 34,22 38,36" fill="white" opacity="0.9"/>
</svg>
</div>
<div class="fm-item-info">
<span class="fm-item-name">photo-001.jpg</span>
<span class="fm-item-meta">3.1 MB</span>
</div>
<span class="fm-item-size">3.1 MB</span>
<span class="fm-item-date">Mar 14, 2026</span>
<span class="fm-item-type-col">Image</span>
</div>
<div class="fm-item" data-name="budget.xlsx" data-type="spreadsheet" data-size="430080" data-date="2026-03-12">
<div class="fm-item-check"><input type="checkbox" /></div>
<div class="fm-item-icon fm-icon-spreadsheet">
<svg width="48" height="48" viewBox="0 0 48 48">
<rect x="6" y="4" width="36" height="40" rx="4" fill="#10b981"/>
<rect x="12" y="12" width="10" height="6" rx="1" fill="white" opacity="0.9"/>
<rect x="26" y="12" width="10" height="6" rx="1" fill="white" opacity="0.9"/>
<rect x="12" y="22" width="10" height="6" rx="1" fill="white" opacity="0.9"/>
<rect x="26" y="22" width="10" height="6" rx="1" fill="white" opacity="0.9"/>
<rect x="12" y="32" width="10" height="6" rx="1" fill="white" opacity="0.9"/>
<rect x="26" y="32" width="10" height="6" rx="1" fill="white" opacity="0.9"/>
</svg>
</div>
<div class="fm-item-info">
<span class="fm-item-name">budget.xlsx</span>
<span class="fm-item-meta">420 KB</span>
</div>
<span class="fm-item-size">420 KB</span>
<span class="fm-item-date">Mar 12, 2026</span>
<span class="fm-item-type-col">Spreadsheet</span>
</div>
<div class="fm-item" data-name="readme.md" data-type="code" data-size="12288" data-date="2026-03-10">
<div class="fm-item-check"><input type="checkbox" /></div>
<div class="fm-item-icon fm-icon-code">
<svg width="48" height="48" viewBox="0 0 48 48">
<rect x="6" y="4" width="36" height="40" rx="4" fill="#6b7280"/>
<text x="24" y="28" text-anchor="middle" fill="white" font-size="10" font-weight="700" font-family="monospace"></></text>
</svg>
</div>
<div class="fm-item-info">
<span class="fm-item-name">readme.md</span>
<span class="fm-item-meta">12 KB</span>
</div>
<span class="fm-item-size">12 KB</span>
<span class="fm-item-date">Mar 10, 2026</span>
<span class="fm-item-type-col">Code</span>
</div>
</div>
</main>
</div>
<!-- Upload Overlay -->
<div class="fm-upload-overlay" id="fm-upload-overlay">
<div class="fm-upload-overlay-content">
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" stroke="#3b82f6" stroke-width="2">
<rect x="8" y="8" width="48" height="48" rx="8" stroke-dasharray="6 4"/>
<line x1="32" y1="20" x2="32" y2="44"/><polyline points="22,30 32,20 42,30"/>
</svg>
<p>Drop files here to upload</p>
</div>
</div>
<!-- Hidden file input -->
<input type="file" id="fm-file-input" multiple hidden />
<!-- Context Menu -->
<div class="fm-context-menu" id="fm-context-menu">
<button class="fm-context-item" data-action="open">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 7h8m0 0L7 4m3 3L7 10"/></svg>
Open
</button>
<button class="fm-context-item" data-action="rename">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 2l3 3-8 8H1v-3z"/></svg>
Rename
</button>
<button class="fm-context-item" data-action="download">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="7" y1="2" x2="7" y2="10"/><polyline points="4,7 7,10 10,7"/><line x1="2" y1="12" x2="12" y2="12"/></svg>
Download
</button>
<button class="fm-context-item" data-action="copy">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="4" y="4" width="8" height="8" rx="1"/><path d="M4 10H3a1 1 0 01-1-1V3a1 1 0 011-1h6a1 1 0 011 1v1"/></svg>
Copy
</button>
<button class="fm-context-item" data-action="move">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 3h4l2-1h5v9H1z"/><line x1="7" y1="6" x2="7" y2="10"/><line x1="5" y1="8" x2="9" y2="8"/></svg>
Move
</button>
<div class="fm-context-divider"></div>
<button class="fm-context-item fm-context-danger" data-action="delete">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 4h8l-.7 8H3.7z"/><line x1="1" y1="4" x2="13" y2="4"/><path d="M5 4V2.5h4V4"/></svg>
Delete
</button>
</div>
<!-- Rename Modal -->
<div class="fm-modal-backdrop" id="fm-rename-modal">
<div class="fm-modal">
<h3 class="fm-modal-title">Rename</h3>
<input type="text" class="fm-modal-input" id="fm-rename-input" />
<div class="fm-modal-actions">
<button class="fm-btn fm-modal-cancel">Cancel</button>
<button class="fm-btn fm-btn-primary fm-modal-confirm">Rename</button>
</div>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>File Manager Page
A full file manager interface with folder tree navigation, grid/list view toggle, drag-and-drop upload, breadcrumb path, and right-click context menu.
Features
- Folder tree sidebar — expandable/collapsible folder structure
- Breadcrumb path — clickable path segments (Home > Documents > Projects)
- Grid/List view — toggle between thumbnail grid and detailed list
- File cards — icon by type (folder, image, PDF, doc, code), name, size, modified date
- Upload — drag-and-drop zone + upload button
- Context menu — right-click for Open, Rename, Delete, Download, Details
- Selection — click to select, shift-click for multi-select, select all checkbox
- Storage indicator — used/total space bar
When to use it
- Cloud storage file browser
- Document management system
- Admin panel file manager