Construction — Materials & Inventory
A rugged jobsite materials inventory page with a searchable table of SKUs, categories, on-hand quantities and suppliers, live category filtering, low-stock and out-of-stock badges, and an order-needed panel that sums every item sitting below its reorder level.
MCP
Code
:root {
--hv: #f5c518;
--steel: #2b3440;
--steel-d: #1b222b;
--bg: #eceae6;
--ink: #1a1d22;
--muted: #666c75;
--orange: #e8642a;
--white: #ffffff;
--line: #d2cfc8;
--radius: 8px;
--shadow: 0 1px 0 rgba(27, 34, 43, 0.06), 0 8px 24px rgba(27, 34, 43, 0.08);
--font: 'Inter', system-ui, -apple-system, sans-serif;
--display: 'Oswald', 'Inter', system-ui, sans-serif;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
font-family: var(--font);
color: var(--ink);
background:
repeating-linear-gradient(45deg, transparent 0 22px, rgba(27, 34, 43, 0.015) 22px 24px),
var(--bg);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.shell {
max-width: 1080px;
margin: 0 auto;
padding: 28px 20px 64px;
}
/* Masthead */
.masthead {
background: var(--steel);
border-radius: var(--radius);
overflow: hidden;
box-shadow: var(--shadow);
}
.masthead__bar {
height: 10px;
background: repeating-linear-gradient(
-45deg,
var(--hv) 0 18px,
var(--steel-d) 18px 36px
);
}
.masthead__row {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
padding: 20px 24px 22px;
flex-wrap: wrap;
}
.masthead__kicker {
margin: 0 0 4px;
font-size: 0.72rem;
letter-spacing: 0.14em;
text-transform: uppercase;
font-weight: 600;
color: var(--hv);
}
.masthead__title {
margin: 0;
font-family: var(--display);
font-weight: 700;
font-size: 2rem;
letter-spacing: 0.01em;
text-transform: uppercase;
color: var(--white);
}
.masthead__updated {
font-size: 0.8rem;
color: #aeb6bf;
white-space: nowrap;
}
/* Summary cards */
.summary {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
margin: 20px 0;
}
.card {
background: var(--white);
border: 1px solid var(--line);
border-left: 4px solid var(--steel);
border-radius: var(--radius);
padding: 16px 18px;
display: flex;
flex-direction: column;
gap: 4px;
box-shadow: var(--shadow);
}
.card--shortfall { border-left-color: var(--orange); }
.card--cta { border-left-color: var(--hv); justify-content: center; gap: 10px; }
.card__label {
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 600;
color: var(--muted);
}
.card__value {
font-family: var(--display);
font-weight: 700;
font-size: 2rem;
line-height: 1;
color: var(--ink);
}
.card--shortfall .card__value { color: var(--orange); }
.card__note { font-size: 0.78rem; color: var(--muted); }
/* Toggle switch */
.switch {
display: inline-flex;
align-items: center;
gap: 10px;
cursor: pointer;
user-select: none;
}
.switch input { position: absolute; opacity: 0; pointer-events: none; }
.switch__track {
position: relative;
width: 42px;
height: 24px;
border-radius: 999px;
background: var(--line);
flex: none;
transition: background 0.18s ease;
}
.switch__track::after {
content: "";
position: absolute;
top: 3px;
left: 3px;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--white);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
transition: transform 0.18s ease;
}
.switch input:checked + .switch__track { background: var(--orange); }
.switch input:checked + .switch__track::after { transform: translateX(18px); }
.switch input:focus-visible + .switch__track {
outline: 2px solid var(--steel);
outline-offset: 2px;
}
.switch__text { font-size: 0.86rem; font-weight: 600; }
/* Controls */
.controls {
display: flex;
gap: 14px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.field { display: flex; flex-direction: column; gap: 6px; }
.field--search { flex: 1 1 280px; }
.field__label {
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
font-weight: 600;
color: var(--muted);
}
.field input,
.field select {
font-family: var(--font);
font-size: 0.95rem;
color: var(--ink);
background: var(--white);
border: 1.5px solid var(--line);
border-radius: var(--radius);
padding: 11px 13px;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.field input:focus,
.field select:focus {
outline: none;
border-color: var(--steel);
box-shadow: 0 0 0 3px rgba(43, 52, 64, 0.15);
}
/* Table */
.tablewrap {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--radius);
overflow-x: auto;
box-shadow: var(--shadow);
}
.grid {
width: 100%;
border-collapse: collapse;
min-width: 760px;
}
.grid thead th {
text-align: left;
font-size: 0.7rem;
letter-spacing: 0.08em;
text-transform: uppercase;
font-weight: 700;
color: #c8cdd3;
background: var(--steel);
padding: 13px 16px;
white-space: nowrap;
}
.grid th.num, .grid td.num { text-align: right; }
.grid tbody td {
padding: 13px 16px;
border-bottom: 1px solid var(--line);
font-size: 0.92rem;
vertical-align: middle;
}
.grid tbody tr:last-child td { border-bottom: none; }
.grid tbody tr { transition: background 0.12s ease; }
.grid tbody tr:hover { background: #f6f5f2; }
.grid tbody tr.is-low { background: #fdf3ec; }
.grid tbody tr.is-low:hover { background: #fbe9dd; }
.grid tbody tr.is-out { background: #fbeae5; }
.sku {
font-family: ui-monospace, "SF Mono", Menlo, monospace;
font-size: 0.84rem;
font-weight: 600;
color: var(--steel);
}
.matname { font-weight: 600; }
.qty { font-weight: 700; font-variant-numeric: tabular-nums; }
.unit { color: var(--muted); font-weight: 400; font-size: 0.84rem; }
.supplier { color: var(--muted); }
.tag {
display: inline-block;
font-size: 0.7rem;
letter-spacing: 0.04em;
text-transform: uppercase;
font-weight: 600;
color: var(--steel);
background: #e4e2dd;
border-radius: 4px;
padding: 3px 8px;
white-space: nowrap;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.72rem;
letter-spacing: 0.04em;
text-transform: uppercase;
font-weight: 700;
padding: 4px 10px;
border-radius: 999px;
white-space: nowrap;
}
.badge::before {
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
}
.badge--ok { color: #2f7d4f; background: #e2f1e7; }
.badge--low { color: var(--orange); background: #fae0d3; }
.badge--out { color: #b6201d; background: #f8d6d3; }
.empty {
padding: 36px 16px;
text-align: center;
color: var(--muted);
font-size: 0.95rem;
}
/* Responsive */
@media (max-width: 760px) {
.summary { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
.shell { padding: 18px 14px 48px; }
.masthead__title { font-size: 1.55rem; }
.summary { grid-template-columns: 1fr 1fr; gap: 10px; }
.card { padding: 13px 14px; }
.card__value { font-size: 1.6rem; }
.card--cta { grid-column: 1 / -1; flex-direction: row; align-items: center; }
.controls { flex-direction: column; }
.field--search { flex: 1 1 auto; }
}(function () {
"use strict";
/** Demo inventory — illustrative data only. */
var MATERIALS = [
{ sku: "LMB-2410", name: '2"x4"x8 KD Stud SPF', category: "Lumber", qty: 420, unit: "ea", reorder: 250, supplier: "Cascade Forest Products" },
{ sku: "LMB-3458", name: '3/4" OSB Sheathing 4x8', category: "Lumber", qty: 38, unit: "sheet", reorder: 60, supplier: "Cascade Forest Products" },
{ sku: "CON-0501", name: "Portland Cement Type I/II 94lb", category: "Concrete", qty: 12, unit: "bag", reorder: 40, supplier: "Granite State Supply" },
{ sku: "CON-0518", name: "Ready-Mix 5000psi", category: "Concrete", qty: 0, unit: "yd³", reorder: 6, supplier: "Granite State Supply" },
{ sku: "REB-0040", name: "#4 Rebar Grade 60 20ft", category: "Concrete", qty: 86, unit: "ea", reorder: 50, supplier: "Ironclad Steel & Rebar" },
{ sku: "FST-1625", name: '3" Exterior Deck Screw (5lb)', category: "Fasteners", qty: 7, unit: "box", reorder: 15, supplier: "Bolt & Bracket Co." },
{ sku: "FST-1208", name: '16d Galv. Framing Nail (50lb)', category: "Fasteners", qty: 4, unit: "box", reorder: 10, supplier: "Bolt & Bracket Co." },
{ sku: "FST-0904", name: '1/2" x 8 Anchor Bolt Galv.', category: "Fasteners", qty: 240, unit: "ea", reorder: 100, supplier: "Bolt & Bracket Co." },
{ sku: "DRY-1258", name: '1/2" Drywall 4x8 Std', category: "Drywall", qty: 64, unit: "sheet", reorder: 80, supplier: "Summit Building Center" },
{ sku: "DRY-1305", name: 'Joint Compound 4.5gal', category: "Drywall", qty: 9, unit: "pail", reorder: 12, supplier: "Summit Building Center" },
{ sku: "INS-0902", name: "R-13 Fiberglass Batt 15in", category: "Insulation", qty: 18, unit: "roll", reorder: 20, supplier: "Summit Building Center" },
{ sku: "ELC-2204", name: '12/2 NM-B Romex 250ft', category: "Electrical", qty: 11, unit: "coil", reorder: 8, supplier: "Voltline Distributors" },
{ sku: "ELC-2380", name: '3/4" EMT Conduit 10ft', category: "Electrical", qty: 5, unit: "ea", reorder: 24, supplier: "Voltline Distributors" },
{ sku: "PLM-3140", name: '3/4" PEX-A Tubing 100ft', category: "Plumbing", qty: 22, unit: "coil", reorder: 10, supplier: "Cascade Plumbing Wholesale" },
{ sku: "PLM-3266", name: '4" PVC DWV Pipe 10ft', category: "Plumbing", qty: 14, unit: "ea", reorder: 16, supplier: "Cascade Plumbing Wholesale" },
{ sku: "PNT-5012", name: "Exterior Acrylic Primer 5gal", category: "Paint", qty: 6, unit: "pail", reorder: 6, supplier: "Truecolor Paint Supply" },
{ sku: "SFT-7701", name: "Hi-Vis Class 2 Vest XL", category: "Safety", qty: 27, unit: "ea", reorder: 20, supplier: "Guardline Safety Gear" },
{ sku: "SFT-7745", name: "Hard Hat Type I White", category: "Safety", qty: 8, unit: "ea", reorder: 15, supplier: "Guardline Safety Gear" }
];
var rowsEl = document.getElementById("rows");
var emptyEl = document.getElementById("empty");
var searchEl = document.getElementById("search");
var categoryEl = document.getElementById("category");
var orderOnlyEl = document.getElementById("orderOnly");
var shortfallCountEl = document.getElementById("shortfallCount");
var shortfallQtyEl = document.getElementById("shortfallQty");
var totalCountEl = document.getElementById("totalCount");
function statusOf(m) {
if (m.qty <= 0) return "out";
if (m.qty <= m.reorder) return "low";
return "ok";
}
var STATUS_TEXT = { ok: "Stocked", low: "Low", out: "Out" };
function buildCategories() {
var seen = {};
MATERIALS.forEach(function (m) { seen[m.category] = true; });
Object.keys(seen).sort().forEach(function (cat) {
var opt = document.createElement("option");
opt.value = cat;
opt.textContent = cat;
categoryEl.appendChild(opt);
});
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
});
}
function rowHtml(m) {
var st = statusOf(m);
return (
'<tr class="' + (st === "ok" ? "" : "is-" + st) + '">' +
'<td><span class="sku">' + escapeHtml(m.sku) + "</span></td>" +
'<td class="matname">' + escapeHtml(m.name) + "</td>" +
'<td><span class="tag">' + escapeHtml(m.category) + "</span></td>" +
'<td class="num"><span class="qty">' + m.qty + '</span> <span class="unit">' + escapeHtml(m.unit) + "</span></td>" +
'<td class="num">' + m.reorder + "</td>" +
'<td class="supplier">' + escapeHtml(m.supplier) + "</td>" +
'<td><span class="badge badge--' + st + '">' + STATUS_TEXT[st] + "</span></td>" +
"</tr>"
);
}
function render() {
var q = searchEl.value.trim().toLowerCase();
var cat = categoryEl.value;
var orderOnly = orderOnlyEl.checked;
var visible = MATERIALS.filter(function (m) {
if (cat !== "all" && m.category !== cat) return false;
if (orderOnly && statusOf(m) === "ok") return false;
if (q) {
var hay = (m.sku + " " + m.name + " " + m.supplier).toLowerCase();
if (hay.indexOf(q) === -1) return false;
}
return true;
});
rowsEl.innerHTML = visible.map(rowHtml).join("");
emptyEl.hidden = visible.length > 0;
// Order-needed summary (always over the full dataset).
var shortCount = 0;
var shortQty = 0;
MATERIALS.forEach(function (m) {
if (statusOf(m) !== "ok") {
shortCount += 1;
var need = m.reorder - m.qty;
if (need > 0) shortQty += need;
}
});
shortfallCountEl.textContent = shortCount;
shortfallQtyEl.textContent = shortQty;
totalCountEl.textContent = MATERIALS.length;
}
buildCategories();
searchEl.addEventListener("input", render);
categoryEl.addEventListener("change", render);
orderOnlyEl.addEventListener("change", render);
render();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Materials & Inventory — Northgate Build Co.</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Oswald:wght@500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="shell">
<header class="masthead">
<div class="masthead__bar" aria-hidden="true"></div>
<div class="masthead__row">
<div>
<p class="masthead__kicker">Northgate Build Co. · Yard 04</p>
<h1 class="masthead__title">Materials & Inventory</h1>
</div>
<span class="masthead__updated">Updated <time datetime="2026-06-22">Jun 22, 2026</time></span>
</div>
</header>
<section class="summary" aria-label="Order needed summary">
<div class="card card--shortfall">
<span class="card__label">Items below reorder</span>
<span class="card__value" id="shortfallCount">0</span>
<span class="card__note">SKUs need ordering</span>
</div>
<div class="card">
<span class="card__label">Units to order</span>
<span class="card__value" id="shortfallQty">0</span>
<span class="card__note">Combined shortfall</span>
</div>
<div class="card">
<span class="card__label">Total SKUs</span>
<span class="card__value" id="totalCount">0</span>
<span class="card__note">Tracked in yard</span>
</div>
<div class="card card--cta">
<span class="card__label">Quick view</span>
<label class="switch">
<input type="checkbox" id="orderOnly" />
<span class="switch__track" aria-hidden="true"></span>
<span class="switch__text">Order needed only</span>
</label>
</div>
</section>
<section class="controls" aria-label="Filter materials">
<div class="field field--search">
<label for="search" class="field__label">Search materials</label>
<input type="search" id="search" placeholder="SKU, name or supplier…" autocomplete="off" />
</div>
<div class="field">
<label for="category" class="field__label">Category</label>
<select id="category">
<option value="all">All categories</option>
</select>
</div>
</section>
<section class="tablewrap" aria-label="Materials inventory">
<table class="grid">
<thead>
<tr>
<th scope="col">SKU</th>
<th scope="col">Material</th>
<th scope="col">Category</th>
<th scope="col" class="num">On hand</th>
<th scope="col" class="num">Reorder</th>
<th scope="col">Supplier</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody id="rows"></tbody>
</table>
<p class="empty" id="empty" hidden>No materials match your filters.</p>
</section>
</main>
<script src="script.js"></script>
</body>
</html>Materials & Inventory
A hi-vis, jobsite-flavored inventory page for tracking construction materials. The table lists every material with its SKU, category, on-hand quantity, unit, reorder level and supplier. Rows automatically flag Low or Out when on-hand stock dips at or below the reorder threshold, with a steel-and-orange status badge so the foreman can scan the board at a glance.
A search box filters across SKU, name and supplier in real time, and a category dropdown narrows the list to lumber, fasteners, concrete and more. An Order needed panel sums every item below its reorder level into a running shortfall count and quantity, and a quick toggle hides everything that is fully stocked so the purchasing list builds itself.
All interactions are vanilla JS — no frameworks, no build step. Drop the three snippets in and the table, filters and order summary work out of the box.
Illustrative UI only — not for real procurement or jobsite use.