UI Components Easy
Attachment List
File attachment list with type icons, file size, download link, remove button, and upload drop zone. CSS-first.
Open in Lab
MCP
css vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #0d1117;
color: #e6edf3;
min-height: 100vh;
padding: 32px 16px;
display: flex;
justify-content: center;
align-items: flex-start;
}
.demo {
width: 100%;
max-width: 560px;
}
.att-panel {
background: #161b22;
border: 1px solid #21262d;
border-radius: 14px;
overflow: hidden;
}
.att-header {
display: flex;
align-items: center;
gap: 10px;
padding: 14px 18px;
border-bottom: 1px solid #21262d;
background: #1c2128;
}
.att-title {
font-size: 14px;
font-weight: 700;
color: #e6edf3;
}
.att-count {
font-size: 12px;
color: #6c7086;
flex: 1;
}
.att-add-btn {
background: #21262d;
border: 1px solid #30363d;
color: #8b949e;
font-size: 12px;
font-weight: 600;
padding: 5px 12px;
border-radius: 7px;
cursor: pointer;
transition: all 0.15s;
}
.att-add-btn:hover {
color: #e6edf3;
border-color: #8b949e;
background: #30363d;
}
.att-list {
padding: 8px 0;
}
.att-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 18px;
transition: background 0.15s;
}
.att-item:hover {
background: rgba(255, 255, 255, 0.03);
}
.att-icon {
width: 36px;
height: 36px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
flex-shrink: 0;
}
.att-icon--pdf {
background: rgba(248, 81, 73, 0.12);
}
.att-icon--doc {
background: rgba(56, 139, 253, 0.12);
}
.att-icon--xls {
background: rgba(63, 185, 80, 0.12);
}
.att-icon--img {
background: rgba(163, 113, 247, 0.12);
}
.att-icon--zip {
background: rgba(210, 153, 34, 0.12);
}
.att-icon--mp4 {
background: rgba(99, 102, 241, 0.12);
}
.att-icon--default {
background: rgba(255, 255, 255, 0.06);
}
.att-info {
flex: 1;
min-width: 0;
}
.att-name {
font-size: 13px;
font-weight: 600;
color: #e6edf3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 2px;
}
.att-size {
font-size: 11px;
color: #6c7086;
}
.att-actions {
display: flex;
gap: 4px;
opacity: 0;
transition: opacity 0.15s;
}
.att-item:hover .att-actions {
opacity: 1;
}
.att-action-btn {
background: none;
border: none;
color: #6c7086;
font-size: 13px;
width: 28px;
height: 28px;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s;
display: flex;
align-items: center;
justify-content: center;
}
.att-action-btn:hover {
color: #e6edf3;
background: rgba(255, 255, 255, 0.06);
}
.att-action-btn--del:hover {
color: #f85149;
background: rgba(248, 81, 73, 0.08);
}
.att-divider {
height: 1px;
background: #21262d;
margin: 4px 0;
}
.att-empty {
padding: 32px;
text-align: center;
color: #4a555f;
font-size: 13px;
}const FILE_ICONS = {
pdf: "๐",
doc: "๐",
docx: "๐",
xls: "๐",
xlsx: "๐",
png: "๐ผ",
jpg: "๐ผ",
gif: "๐ผ",
webp: "๐ผ",
svg: "๐ผ",
zip: "๐",
tar: "๐",
gz: "๐",
mp4: "๐ฌ",
mov: "๐ฌ",
mp3: "๐ต",
default: "๐",
};
const FILE_CLS = {
pdf: "pdf",
doc: "doc",
docx: "doc",
xls: "xls",
xlsx: "xls",
png: "img",
jpg: "img",
gif: "img",
webp: "img",
svg: "img",
zip: "zip",
tar: "zip",
gz: "zip",
mp4: "mp4",
mov: "mp4",
};
let files = [
{ id: 1, name: "contract_signed.pdf", size: "1.2 MB" },
{ id: 2, name: "meeting_notes.docx", size: "48 KB" },
{ id: 3, name: "budget_q1.xlsx", size: "324 KB" },
{ id: 4, name: "hero_banner.png", size: "2.4 MB" },
{ id: 5, name: "source_code.zip", size: "18.6 MB" },
{ id: 6, name: "demo_recording.mp4", size: "94.2 MB" },
];
const DEMO_NAMES = ["design_mockup.png", "api_spec.pdf", "data_export.csv", "readme.md"];
let demoIdx = 0;
function getExt(name) {
return name.split(".").pop().toLowerCase();
}
function renderList() {
const list = document.getElementById("attList");
const count = document.getElementById("attCount");
count.textContent = `${files.length} file${files.length !== 1 ? "s" : ""}`;
if (files.length === 0) {
list.innerHTML = '<div class="att-empty">No attachments yet</div>';
return;
}
list.innerHTML = "";
files.forEach((f, idx) => {
const ext = getExt(f.name);
const icon = FILE_ICONS[ext] || FILE_ICONS.default;
const cls = FILE_CLS[ext] || "default";
const item = document.createElement("div");
item.className = "att-item";
item.innerHTML = `
<div class="att-icon att-icon--${cls}">${icon}</div>
<div class="att-info">
<div class="att-name">${f.name}</div>
<div class="att-size">${f.size}</div>
</div>
<div class="att-actions">
<button class="att-action-btn" title="Download" data-id="${f.id}">โฌ</button>
<button class="att-action-btn att-action-btn--del" title="Remove" data-id="${f.id}" data-action="remove">โ</button>
</div>
`;
if (idx < files.length - 1) {
const div = document.createElement("div");
div.className = "att-divider";
list.appendChild(item);
list.appendChild(div);
} else {
list.appendChild(item);
}
});
}
document.getElementById("attList").addEventListener("click", (e) => {
const btn = e.target.closest('[data-action="remove"]');
if (!btn) return;
const id = parseInt(btn.dataset.id);
files = files.filter((f) => f.id !== id);
renderList();
});
document.getElementById("attAddBtn").addEventListener("click", () => {
const name = DEMO_NAMES[demoIdx % DEMO_NAMES.length];
demoIdx++;
const sizes = ["12 KB", "450 KB", "2.1 MB", "88 KB"];
files.push({ id: Date.now(), name, size: sizes[demoIdx % sizes.length] });
renderList();
});
renderList();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Attachment List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<div class="att-panel">
<div class="att-header">
<span class="att-title">Attachments</span>
<span class="att-count" id="attCount">0 files</span>
<button class="att-add-btn" id="attAddBtn">+ Add files</button>
</div>
<div class="att-list" id="attList"></div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>File attachment list with colored file-type icons (PDF, DOCX, XLSX, PNG, ZIP), file name, size, and upload date. Includes a remove button per item and an empty-state drop zone for adding new files.