Job Board — Job Listing Card
A reusable, scannable job listing card for a careers board or ATS. Each row pairs a colored company logo with the role title, company, location and a green salary chip, plus status badges for new, urgent and remote roles. A bookmark toggle tracks saved jobs in a live header counter, quick-apply turns into an applied confirmation, and chip filters and search narrow the list. Clean, accessible, responsive vanilla HTML, CSS and JavaScript with no dependencies.
MCP
Code
:root {
--brand: #2563eb;
--brand-d: #1d4ed8;
--brand-50: #eaf1ff;
--ink: #0f172a;
--ink-2: #475569;
--muted: #64748b;
--bg: #f6f8fb;
--surface: #ffffff;
--line: rgba(15, 23, 42, 0.1);
--line-2: rgba(15, 23, 42, 0.18);
--ok: #16a34a;
--warn: #d97706;
--danger: #dc2626;
--new: #2563eb;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-1: 0 1px 2px rgba(15, 23, 42, 0.04), 0 1px 3px rgba(15, 23, 42, 0.06);
--sh-2: 0 6px 18px rgba(15, 23, 42, 0.08), 0 2px 6px rgba(15, 23, 42, 0.05);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.skip {
position: absolute;
left: -999px;
top: 0;
background: var(--ink);
color: #fff;
padding: 10px 16px;
border-radius: 0 0 var(--r-sm) 0;
z-index: 50;
}
.skip:focus { left: 0; }
/* ---------- Top bar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 20;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: saturate(1.2) blur(10px);
border-bottom: 1px solid var(--line);
}
.topbar-inner {
max-width: 920px;
margin: 0 auto;
padding: 12px 20px;
display: flex;
align-items: center;
gap: 14px;
}
.brand {
display: flex;
align-items: center;
gap: 9px;
font-weight: 700;
white-space: nowrap;
}
.brand-mark {
display: grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: 10px;
background: linear-gradient(135deg, var(--brand), var(--brand-d));
color: #fff;
box-shadow: var(--sh-1);
}
.brand-name { font-size: 15px; letter-spacing: -0.01em; }
.brand-name b { color: var(--brand); font-weight: 800; }
.search {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 8px;
padding: 0 12px;
height: 40px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 999px;
color: var(--muted);
transition: border-color 0.15s, box-shadow 0.15s;
}
.search:focus-within {
border-color: var(--brand);
box-shadow: 0 0 0 3px var(--brand-50);
}
.search input {
flex: 1;
min-width: 0;
border: 0;
outline: 0;
background: none;
font: inherit;
font-size: 14px;
color: var(--ink);
}
.saved-pill {
display: inline-flex;
align-items: center;
gap: 7px;
height: 40px;
padding: 0 14px;
border-radius: 999px;
border: 1px solid var(--line);
background: var(--surface);
color: var(--ink-2);
font: inherit;
font-size: 13px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
}
.saved-pill b { color: var(--brand); }
.saved-pill:hover { border-color: var(--line-2); }
/* ---------- Layout ---------- */
.wrap {
max-width: 920px;
margin: 0 auto;
padding: 28px 20px 64px;
}
.head-row {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 18px;
}
h1 {
margin: 0;
font-size: 26px;
font-weight: 800;
letter-spacing: -0.02em;
}
.sub {
margin: 4px 0 0;
color: var(--muted);
font-size: 13.5px;
}
.filters {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.chip-filter {
border: 1px solid var(--line);
background: var(--surface);
color: var(--ink-2);
padding: 7px 14px;
border-radius: 999px;
font: inherit;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s;
}
.chip-filter:hover { border-color: var(--line-2); color: var(--ink); }
.chip-filter.is-active {
background: var(--ink);
border-color: var(--ink);
color: #fff;
}
/* ---------- Job list ---------- */
.job-list {
display: grid;
gap: 12px;
}
.job-card {
position: relative;
display: grid;
grid-template-columns: 52px 1fr auto;
grid-template-areas:
"logo body apply"
"logo meta apply";
gap: 4px 16px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px;
box-shadow: var(--sh-1);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.job-card:hover {
transform: translateY(-2px);
box-shadow: var(--sh-2);
border-color: var(--line-2);
}
.job-card.is-urgent { border-left: 3px solid var(--danger); }
.logo {
grid-area: logo;
width: 52px;
height: 52px;
border-radius: 12px;
display: grid;
place-items: center;
font-weight: 800;
font-size: 17px;
color: #fff;
letter-spacing: -0.02em;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
}
.body { grid-area: body; min-width: 0; }
.title-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.job-title {
margin: 0;
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--ink);
}
.company {
margin: 2px 0 0;
font-size: 13.5px;
color: var(--ink-2);
font-weight: 500;
}
.company .dot { color: var(--line-2); margin: 0 6px; }
.posted { color: var(--muted); }
.badge {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 11px;
font-weight: 700;
padding: 2px 8px;
border-radius: 999px;
letter-spacing: 0.02em;
text-transform: uppercase;
line-height: 1.6;
}
.badge.new { background: var(--brand-50); color: var(--brand-d); }
.badge.urgent { background: #fef2f2; color: var(--danger); }
.badge.remote { background: #f0fdf4; color: var(--ok); }
.meta {
grid-area: meta;
margin-top: 10px;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.tag {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 12.5px;
font-weight: 600;
color: var(--ink-2);
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 4px 9px;
}
.tag svg { color: var(--muted); }
.tag.salary {
color: #166534;
background: #f0fdf4;
border-color: rgba(22, 163, 74, 0.18);
}
.tag.salary svg { color: var(--ok); }
.tag.skill {
background: var(--surface);
color: var(--ink-2);
}
.apply {
grid-area: apply;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
gap: 12px;
}
.save-btn {
display: grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: 50%;
border: 1px solid var(--line);
background: var(--surface);
color: var(--muted);
cursor: pointer;
transition: all 0.15s;
}
.save-btn:hover { border-color: var(--line-2); color: var(--ink); background: var(--bg); }
.save-btn svg { transition: transform 0.15s; }
.save-btn[aria-pressed="true"] {
color: var(--brand);
border-color: rgba(37, 99, 235, 0.3);
background: var(--brand-50);
}
.save-btn[aria-pressed="true"] svg { fill: var(--brand); transform: scale(1.05); }
.apply-btn {
display: inline-flex;
align-items: center;
gap: 6px;
height: 38px;
padding: 0 18px;
border-radius: var(--r-sm);
border: 0;
background: var(--brand);
color: #fff;
font: inherit;
font-size: 13.5px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s, transform 0.1s;
}
.apply-btn:hover { background: var(--brand-d); }
.apply-btn:active { transform: scale(0.97); }
.apply-btn.is-applied {
background: #f0fdf4;
color: #166534;
cursor: default;
box-shadow: inset 0 0 0 1px rgba(22, 163, 74, 0.25);
}
button:focus-visible,
input:focus-visible,
.link-btn:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
}
.empty {
text-align: center;
color: var(--muted);
padding: 40px 0;
font-size: 14.5px;
}
.link-btn {
border: 0;
background: none;
color: var(--brand);
font: inherit;
font-weight: 600;
cursor: pointer;
text-decoration: underline;
padding: 0;
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translateX(-50%) translateY(20px);
background: var(--ink);
color: #fff;
font-size: 13.5px;
font-weight: 500;
padding: 11px 18px;
border-radius: 999px;
box-shadow: var(--sh-2);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s, transform 0.2s;
z-index: 60;
}
.toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* ---------- Responsive ---------- */
@media (max-width: 720px) {
.search { order: 3; flex-basis: 100%; }
.topbar-inner { flex-wrap: wrap; }
}
@media (max-width: 520px) {
.wrap { padding: 20px 14px 56px; }
.job-card {
grid-template-columns: 44px 1fr;
grid-template-areas:
"logo body"
"meta meta"
"apply apply";
padding: 16px;
gap: 4px 12px;
}
.logo { width: 44px; height: 44px; font-size: 15px; }
.apply {
flex-direction: row-reverse;
align-items: center;
justify-content: flex-start;
margin-top: 12px;
}
.apply-btn { flex: 1; justify-content: center; }
h1 { font-size: 22px; }
}// Job Board — Job Listing Card (vanilla JS, no deps)
(function () {
"use strict";
var JOBS = [
{
id: "j1", title: "Senior Frontend Engineer", company: "Lumio Health",
logo: "LH", color: "#2563eb", location: "Berlin, DE", remote: true,
salary: "€78k–96k", type: "Full-time", posted: "2h ago",
skills: ["React", "TypeScript"], new: true, urgent: false, saved: false
},
{
id: "j2", title: "Product Designer", company: "Fernway Studio",
logo: "FS", color: "#7c3aed", location: "Lisbon, PT", remote: true,
salary: "€55k–70k", type: "Full-time", posted: "5h ago",
skills: ["Figma", "Design Systems"], new: true, urgent: false, saved: false
},
{
id: "j3", title: "DevOps Engineer", company: "Northgate Cloud",
logo: "NC", color: "#0891b2", location: "Remote · EU", remote: true,
salary: "€85k–110k", type: "Contract", posted: "1d ago",
skills: ["Kubernetes", "AWS"], new: false, urgent: true, saved: false
},
{
id: "j4", title: "Customer Success Lead", company: "Brightloop",
logo: "BL", color: "#d97706", location: "Madrid, ES", remote: false,
salary: "€48k–60k", type: "Full-time", posted: "1d ago",
skills: ["SaaS", "Onboarding"], new: false, urgent: false, saved: true
},
{
id: "j5", title: "Data Analyst", company: "Quantia Labs",
logo: "QL", color: "#16a34a", location: "Amsterdam, NL", remote: true,
salary: "€52k–68k", type: "Full-time", posted: "2d ago",
skills: ["SQL", "Python"], new: false, urgent: false, saved: false
},
{
id: "j6", title: "Backend Engineer (Go)", company: "Orbital Pay",
logo: "OP", color: "#dc2626", location: "Remote · Global", remote: true,
salary: "€90k–120k", type: "Full-time", posted: "3d ago",
skills: ["Go", "PostgreSQL"], new: false, urgent: true, saved: false
},
{
id: "j7", title: "Marketing Manager", company: "Velda & Co",
logo: "VC", color: "#db2777", location: "Paris, FR", remote: false,
salary: "€50k–65k", type: "Full-time", posted: "4d ago",
skills: ["Growth", "SEO"], new: false, urgent: false, saved: false
},
{
id: "j8", title: "Junior QA Tester", company: "Pinecrest Apps",
logo: "PA", color: "#0f766e", location: "Dublin, IE", remote: true,
salary: "€34k–42k", type: "Part-time", posted: "6d ago",
skills: ["Cypress", "Manual QA"], new: false, urgent: false, saved: false
}
];
var listEl = document.getElementById("results");
var emptyEl = document.getElementById("empty");
var savedNumEl = document.getElementById("savedNum");
var resultLabel = document.getElementById("resultLabel");
var toastEl = document.getElementById("toast");
var qInput = document.getElementById("q");
var filterBtns = Array.prototype.slice.call(document.querySelectorAll(".chip-filter"));
var activeFilter = "all";
var query = "";
var toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2200);
}
function esc(s) {
return String(s).replace(/[&<>"]/g, function (c) {
return { "&": "&", "<": "<", ">": ">", '"': """ }[c];
});
}
function pinIcon() {
return '<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 12-9 12s-9-5-9-12a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>';
}
function moneyIcon() {
return '<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1v22M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>';
}
function clockIcon() {
return '<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>';
}
function bookmarkIcon() {
return '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>';
}
function cardHTML(j) {
var badges = "";
if (j.new) badges += '<span class="badge new">New</span>';
if (j.urgent) badges += '<span class="badge urgent">Urgent</span>';
if (j.remote) badges += '<span class="badge remote">Remote</span>';
var skills = j.skills.map(function (s) {
return '<span class="tag skill">' + esc(s) + "</span>";
}).join("");
return (
'<article class="job-card' + (j.urgent ? " is-urgent" : "") + '" data-id="' + j.id + '">' +
'<div class="logo" style="background:' + j.color + '">' + esc(j.logo) + "</div>" +
'<div class="body">' +
'<div class="title-row"><h2 class="job-title">' + esc(j.title) + "</h2>" + badges + "</div>" +
'<p class="company">' + esc(j.company) +
'<span class="dot">·</span><span class="posted">' + esc(j.posted) + "</span></p>" +
"</div>" +
'<div class="meta">' +
'<span class="tag salary">' + moneyIcon() + esc(j.salary) + "</span>" +
'<span class="tag">' + pinIcon() + esc(j.location) + "</span>" +
'<span class="tag">' + clockIcon() + esc(j.type) + "</span>" +
skills +
"</div>" +
'<div class="apply">' +
'<button class="save-btn" data-act="save" aria-pressed="' + (j.saved ? "true" : "false") +
'" aria-label="' + (j.saved ? "Remove bookmark" : "Save job") + '" title="Save job">' +
bookmarkIcon() +
"</button>" +
'<button class="apply-btn" data-act="apply">Quick apply</button>' +
"</div>" +
"</article>"
);
}
function matches(j) {
if (activeFilter === "new" && !j.new) return false;
if (activeFilter === "remote" && !j.remote) return false;
if (activeFilter === "urgent" && !j.urgent) return false;
if (activeFilter === "saved" && !j.saved) return false;
if (query) {
var hay = (j.title + " " + j.company + " " + j.location + " " + j.skills.join(" ")).toLowerCase();
if (hay.indexOf(query) === -1) return false;
}
return true;
}
function render() {
var visible = JOBS.filter(matches);
listEl.innerHTML = visible.map(cardHTML).join("");
emptyEl.hidden = visible.length !== 0;
var n = visible.length;
resultLabel.textContent = n + (n === 1 ? " position" : " positions") + " · updated moments ago";
updateSavedCount();
}
function updateSavedCount() {
var c = JOBS.filter(function (j) { return j.saved; }).length;
savedNumEl.textContent = c;
}
// Event delegation for save / apply
listEl.addEventListener("click", function (e) {
var btn = e.target.closest("[data-act]");
if (!btn) return;
var card = e.target.closest(".job-card");
if (!card) return;
var job = JOBS.find(function (j) { return j.id === card.dataset.id; });
if (!job) return;
if (btn.dataset.act === "save") {
job.saved = !job.saved;
btn.setAttribute("aria-pressed", job.saved ? "true" : "false");
btn.setAttribute("aria-label", job.saved ? "Remove bookmark" : "Save job");
updateSavedCount();
toast(job.saved ? "Saved “" + job.title + "”" : "Removed from saved");
if (activeFilter === "saved" && !job.saved) {
setTimeout(render, 180);
}
} else if (btn.dataset.act === "apply") {
if (btn.classList.contains("is-applied")) return;
btn.classList.add("is-applied");
btn.textContent = "Applied ✓";
toast("Application sent to " + job.company);
}
});
// Filters
filterBtns.forEach(function (b) {
b.addEventListener("click", function () {
filterBtns.forEach(function (x) {
x.classList.remove("is-active");
x.setAttribute("aria-selected", "false");
});
b.classList.add("is-active");
b.setAttribute("aria-selected", "true");
activeFilter = b.dataset.filter;
render();
});
});
// Search (debounced)
var searchTimer = null;
qInput.addEventListener("input", function () {
clearTimeout(searchTimer);
searchTimer = setTimeout(function () {
query = qInput.value.trim().toLowerCase();
render();
}, 140);
});
// Reset link inside empty state
document.getElementById("clearFilter").addEventListener("click", function () {
activeFilter = "all";
query = "";
qInput.value = "";
filterBtns.forEach(function (x) {
var on = x.dataset.filter === "all";
x.classList.toggle("is-active", on);
x.setAttribute("aria-selected", on ? "true" : "false");
});
render();
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Job Board — Job Listing Card</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;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip" href="#results">Skip to listings</a>
<header class="topbar">
<div class="topbar-inner">
<div class="brand">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
</span>
<span class="brand-name">Northwind <b>Careers</b></span>
</div>
<div class="search">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<input id="q" type="search" placeholder="Search role, company, location…" aria-label="Search jobs" />
</div>
<button class="saved-pill" id="savedCount" aria-live="polite">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
<span><b id="savedNum">0</b> saved</span>
</button>
</div>
</header>
<main class="wrap">
<div class="head-row">
<div>
<h1>Open roles</h1>
<p class="sub" id="resultLabel">8 positions · updated moments ago</p>
</div>
<div class="filters" role="tablist" aria-label="Filter jobs">
<button class="chip-filter is-active" data-filter="all" role="tab" aria-selected="true">All</button>
<button class="chip-filter" data-filter="new" role="tab" aria-selected="false">New</button>
<button class="chip-filter" data-filter="remote" role="tab" aria-selected="false">Remote</button>
<button class="chip-filter" data-filter="urgent" role="tab" aria-selected="false">Urgent</button>
<button class="chip-filter" data-filter="saved" role="tab" aria-selected="false">Saved</button>
</div>
</div>
<section id="results" class="job-list" aria-label="Job listings"></section>
<p class="empty" id="empty" hidden>No roles match those filters. <button id="clearFilter" class="link-btn">Reset</button></p>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Job Listing Card
A clean, professional job listing card pattern for a careers board or applicant tracking system. Each card leads with a colored company logo, then the role title, company and posting time, followed by a row of scannable chips: a green salary chip, a location chip with a remote badge, the employment type, and key skills. Status pills call out New, Urgent and Remote roles at a glance, and urgent listings get a red accent edge so they stand out in a dense list.
The list ships with eight realistic, clearly fictional roles covering the common states — new, urgent, remote and already-saved. A circular bookmark button toggles the saved state with a filled-in active style and updates a live counter in the header. Quick-apply sends a confirmation toast and locks into an “Applied” state so the same role can’t be submitted twice.
Filter chips (All, New, Remote, Urgent, Saved) and a debounced search box narrow the list across titles, companies, locations and skills, with a friendly empty state and reset. Everything is built with semantic landmarks, ARIA pressed/selected states, visible focus rings and WCAG-AA contrast, and the layout reflows cleanly from desktop down to roughly 360px.
Illustrative UI only — fictional jobs & companies, not a real hiring platform.