Pages Medium
Changelog Page
A version-based changelog page with timeline, version badges, categorized changes (added, changed, fixed, removed), and filter by type. No libraries.
Open in Lab
MCP
vanilla-js css
Targets: JS HTML
Code
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
sans-serif;
background: #f8fafc;
color: #1e293b;
line-height: 1.6;
min-height: 100vh;
}
.changelog {
max-width: 760px;
margin: 0 auto;
padding: 48px 24px 80px;
overflow-x: hidden;
}
/* Header */
.changelog-header {
text-align: center;
margin-bottom: 40px;
}
.changelog-header h1 {
font-size: 2.5rem;
font-weight: 800;
letter-spacing: -0.025em;
color: #0f172a;
}
.changelog-subtitle {
font-size: 1.125rem;
color: #64748b;
margin-top: 8px;
}
/* Controls */
.changelog-controls {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
justify-content: space-between;
margin-bottom: 40px;
}
.filter-pills {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.filter-pill {
padding: 6px 16px;
border-radius: 9999px;
border: 1.5px solid #e2e8f0;
background: #fff;
color: #64748b;
font-size: 0.8125rem;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.filter-pill:hover {
border-color: #94a3b8;
color: #334155;
}
.filter-pill.active {
background: #0f172a;
border-color: #0f172a;
color: #fff;
}
.filter-pill[data-filter="added"].active {
background: #16a34a;
border-color: #16a34a;
}
.filter-pill[data-filter="changed"].active {
background: #2563eb;
border-color: #2563eb;
}
.filter-pill[data-filter="fixed"].active {
background: #ca8a04;
border-color: #ca8a04;
}
.filter-pill[data-filter="removed"].active {
background: #dc2626;
border-color: #dc2626;
}
.search-wrapper {
position: relative;
}
.search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #94a3b8;
pointer-events: none;
}
.search-input {
padding: 8px 14px 8px 36px;
border: 1.5px solid #e2e8f0;
border-radius: 8px;
font-size: 0.875rem;
background: #fff;
color: #1e293b;
width: 220px;
transition: border-color 0.15s ease;
}
.search-input::placeholder {
color: #94a3b8;
}
.search-input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Timeline */
.timeline {
position: relative;
padding-left: 32px;
overflow: visible;
}
.timeline::before {
content: "";
position: absolute;
left: 7px;
top: 8px;
bottom: 8px;
width: 2px;
background: #e2e8f0;
border-radius: 1px;
}
/* Timeline Entry */
.timeline-entry {
position: relative;
margin-bottom: 32px;
transition: opacity 0.3s ease;
}
.timeline-entry.hidden {
display: none;
}
.timeline-entry:last-child {
margin-bottom: 0;
}
.timeline-node {
position: absolute;
left: -32px;
top: 24px;
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
border: 3px solid #94a3b8;
z-index: 1;
transform: translateX(-0.5px);
}
.timeline-node.node-major {
border-color: #3b82f6;
background: #3b82f6;
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2);
}
/* Card */
.timeline-card {
background: #fff;
border-radius: 12px;
border: 1px solid #e2e8f0;
padding: 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
transition: box-shadow 0.2s ease;
}
.timeline-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
/* Collapsed state */
.timeline-card.collapsed .change-list {
max-height: 0;
overflow: hidden;
opacity: 0;
margin-top: 0;
}
.timeline-card.collapsed .version-header {
opacity: 0.7;
}
.timeline-card.collapsed .expand-btn svg {
transform: rotate(0deg);
}
.timeline-card:not(.collapsed) .expand-btn svg {
transform: rotate(180deg);
}
/* Version Header */
.version-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
transition: opacity 0.2s ease;
}
.version-info {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
min-width: 0;
}
.version-badge {
display: inline-block;
padding: 4px 12px;
background: #0f172a;
color: #fff;
font-size: 0.875rem;
font-weight: 700;
border-radius: 6px;
font-family: "SF Mono", SFMono-Regular, ui-monospace, monospace;
letter-spacing: -0.01em;
white-space: nowrap;
}
.version-date {
font-size: 0.875rem;
color: #64748b;
white-space: nowrap;
}
.major-label {
padding: 3px 10px;
background: #eff6ff;
color: #2563eb;
font-size: 0.75rem;
font-weight: 600;
border-radius: 9999px;
border: 1px solid #bfdbfe;
white-space: nowrap;
}
.expand-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: 1px solid #e2e8f0;
border-radius: 8px;
background: #fff;
color: #64748b;
cursor: pointer;
transition: all 0.15s ease;
flex-shrink: 0;
}
.expand-btn:hover {
background: #f1f5f9;
color: #334155;
}
.expand-btn svg {
transition: transform 0.25s ease;
}
/* Change List */
.change-list {
list-style: none;
margin-top: 20px;
display: flex;
flex-direction: column;
gap: 10px;
transition: max-height 0.35s ease, opacity 0.25s ease, margin-top 0.35s ease;
max-height: 600px;
opacity: 1;
}
.change-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 6px 0;
transition: opacity 0.2s ease;
}
.change-item.hidden {
display: none;
}
.change-badge {
display: inline-block;
padding: 2px 10px;
border-radius: 9999px;
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
flex-shrink: 0;
margin-top: 2px;
}
.badge-added {
background: #dcfce7;
color: #15803d;
}
.badge-changed {
background: #dbeafe;
color: #1d4ed8;
}
.badge-fixed {
background: #fef9c3;
color: #a16207;
}
.badge-removed {
background: #fee2e2;
color: #dc2626;
}
.change-text {
font-size: 0.9375rem;
color: #334155;
line-height: 1.5;
min-width: 0;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* Responsive */
@media (max-width: 640px) {
.changelog {
padding: 32px 12px 60px;
}
.changelog-header h1 {
font-size: 1.75rem;
}
.changelog-controls {
flex-direction: column;
align-items: stretch;
}
.filter-pills {
gap: 6px;
justify-content: center;
}
.filter-pill {
padding: 5px 12px;
font-size: 0.75rem;
}
.search-input {
width: 100%;
}
.timeline {
padding-left: 24px;
}
.timeline::before {
left: 5px;
}
.timeline-node {
left: -24px;
width: 12px;
height: 12px;
border-width: 2.5px;
transform: translateX(0);
}
.timeline-node.node-major {
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}
.timeline-card {
padding: 14px;
}
.version-info {
gap: 6px;
}
.version-badge {
padding: 3px 8px;
font-size: 0.8rem;
}
.version-date {
font-size: 0.8rem;
}
.major-label {
padding: 2px 8px;
font-size: 0.7rem;
}
.change-list {
margin-top: 14px;
gap: 8px;
}
.change-item {
flex-direction: column;
gap: 4px;
}
.change-badge {
margin-top: 0;
font-size: 0.625rem;
padding: 2px 8px;
}
.change-text {
font-size: 0.875rem;
}
}
@media (max-width: 380px) {
.changelog {
padding: 24px 8px 48px;
}
.changelog-header h1 {
font-size: 1.5rem;
}
.changelog-subtitle {
font-size: 1rem;
}
.timeline {
padding-left: 20px;
}
.timeline::before {
left: 3px;
}
.timeline-node {
left: -20px;
width: 10px;
height: 10px;
}
.timeline-card {
padding: 12px;
}
.version-info {
flex-direction: column;
gap: 4px;
}
.timeline-entry {
margin-bottom: 20px;
}
}(function () {
const filterPills = document.querySelectorAll(".filter-pill");
const searchInput = document.querySelector(".search-input");
const entries = document.querySelectorAll(".timeline-entry");
let activeFilter = "all";
// Filter pills
filterPills.forEach(function (pill) {
pill.addEventListener("click", function () {
filterPills.forEach(function (p) {
p.classList.remove("active");
});
pill.classList.add("active");
activeFilter = pill.getAttribute("data-filter");
applyFilters();
});
});
// Search
searchInput.addEventListener("input", function () {
applyFilters();
});
function applyFilters() {
var query = searchInput.value.toLowerCase().trim();
entries.forEach(function (entry) {
var items = entry.querySelectorAll(".change-item");
var hasVisibleItem = false;
items.forEach(function (item) {
var type = item.getAttribute("data-type");
var text = item.querySelector(".change-text").textContent.toLowerCase();
var matchesFilter = activeFilter === "all" || type === activeFilter;
var matchesSearch = !query || text.indexOf(query) !== -1;
if (matchesFilter && matchesSearch) {
item.classList.remove("hidden");
hasVisibleItem = true;
} else {
item.classList.add("hidden");
}
});
if (hasVisibleItem) {
entry.classList.remove("hidden");
} else {
entry.classList.add("hidden");
}
});
}
// Expand / Collapse
document.querySelectorAll(".expand-btn").forEach(function (btn) {
btn.addEventListener("click", function () {
var card = btn.closest(".timeline-card");
var entry = btn.closest(".timeline-entry");
var isCollapsed = card.classList.contains("collapsed");
if (isCollapsed) {
card.classList.remove("collapsed");
entry.setAttribute("data-expanded", "true");
btn.setAttribute("aria-label", "Collapse version");
} else {
card.classList.add("collapsed");
entry.setAttribute("data-expanded", "false");
btn.setAttribute("aria-label", "Expand version");
}
});
});
// Smooth scroll to version via anchor hash
if (window.location.hash) {
var version = window.location.hash.replace("#", "");
var target = document.querySelector('[data-version="' + version + '"]');
if (target) {
target.scrollIntoView({ behavior: "smooth", block: "center" });
var card = target.querySelector(".timeline-card");
if (card && card.classList.contains("collapsed")) {
card.classList.remove("collapsed");
target.setAttribute("data-expanded", "true");
}
}
}
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Changelog</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="changelog">
<header class="changelog-header">
<h1>Changelog</h1>
<p class="changelog-subtitle">See what's new in each release</p>
</header>
<div class="changelog-controls">
<div class="filter-pills">
<button class="filter-pill active" data-filter="all">All</button>
<button class="filter-pill" data-filter="added">Added</button>
<button class="filter-pill" data-filter="changed">Changed</button>
<button class="filter-pill" data-filter="fixed">Fixed</button>
<button class="filter-pill" data-filter="removed">Removed</button>
</div>
<div class="search-wrapper">
<svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" class="search-input" placeholder="Search changes..." />
</div>
</div>
<div class="timeline">
<!-- v3.2.0 -->
<div class="timeline-entry" data-version="3.2.0" data-expanded="true">
<div class="timeline-node"></div>
<div class="timeline-card">
<div class="version-header">
<div class="version-info">
<span class="version-badge">v3.2.0</span>
<span class="version-date">March 15, 2026</span>
</div>
</div>
<ul class="change-list">
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Dark mode support with automatic system preference detection</span>
</li>
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Export dashboard data to CSV and PDF formats</span>
</li>
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Keyboard shortcuts for common actions with customizable bindings</span>
</li>
<li class="change-item" data-type="changed">
<span class="change-badge badge-changed">Changed</span>
<span class="change-text">Redesigned settings panel with tabbed navigation</span>
</li>
<li class="change-item" data-type="changed">
<span class="change-badge badge-changed">Changed</span>
<span class="change-text">Upgraded authentication flow to support passkeys</span>
</li>
<li class="change-item" data-type="fixed">
<span class="change-badge badge-fixed">Fixed</span>
<span class="change-text">Resolved an issue where notifications were duplicated on mobile devices</span>
</li>
</ul>
</div>
</div>
<!-- v3.1.0 -->
<div class="timeline-entry" data-version="3.1.0" data-expanded="true">
<div class="timeline-node"></div>
<div class="timeline-card">
<div class="version-header">
<div class="version-info">
<span class="version-badge">v3.1.0</span>
<span class="version-date">February 8, 2026</span>
</div>
</div>
<ul class="change-list">
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Real-time collaboration with presence indicators</span>
</li>
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Webhook integrations for Slack and Microsoft Teams</span>
</li>
<li class="change-item" data-type="fixed">
<span class="change-badge badge-fixed">Fixed</span>
<span class="change-text">Chart rendering glitch when switching between date ranges</span>
</li>
<li class="change-item" data-type="removed">
<span class="change-badge badge-removed">Removed</span>
<span class="change-text">Deprecated legacy API v1 endpoints (migrated to v2)</span>
</li>
</ul>
</div>
</div>
<!-- v3.0.0 -->
<div class="timeline-entry" data-version="3.0.0" data-expanded="true">
<div class="timeline-node node-major"></div>
<div class="timeline-card">
<div class="version-header">
<div class="version-info">
<span class="version-badge">v3.0.0</span>
<span class="version-date">January 12, 2026</span>
<span class="major-label">Major Release</span>
</div>
</div>
<ul class="change-list">
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Complete UI overhaul with new design system</span>
</li>
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Multi-workspace support for enterprise accounts</span>
</li>
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Advanced analytics dashboard with custom report builder</span>
</li>
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Role-based access control with granular permissions</span>
</li>
<li class="change-item" data-type="changed">
<span class="change-badge badge-changed">Changed</span>
<span class="change-text">Migrated database layer to PostgreSQL for improved performance</span>
</li>
<li class="change-item" data-type="changed">
<span class="change-badge badge-changed">Changed</span>
<span class="change-text">API response format now uses JSON:API specification</span>
</li>
<li class="change-item" data-type="changed">
<span class="change-badge badge-changed">Changed</span>
<span class="change-text">Minimum Node.js version bumped to 20 LTS</span>
</li>
<li class="change-item" data-type="fixed">
<span class="change-badge badge-fixed">Fixed</span>
<span class="change-text">Memory leak in long-running WebSocket connections</span>
</li>
<li class="change-item" data-type="fixed">
<span class="change-badge badge-fixed">Fixed</span>
<span class="change-text">Incorrect timezone handling for scheduled tasks</span>
</li>
</ul>
</div>
</div>
<!-- v2.9.0 -->
<div class="timeline-entry" data-version="2.9.0" data-expanded="false">
<div class="timeline-node"></div>
<div class="timeline-card collapsed">
<div class="version-header">
<div class="version-info">
<span class="version-badge">v2.9.0</span>
<span class="version-date">December 5, 2025</span>
</div>
<button class="expand-btn" aria-label="Expand version">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</button>
</div>
<ul class="change-list">
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Bulk import tool for migrating data from other platforms</span>
</li>
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Two-factor authentication via authenticator apps</span>
</li>
<li class="change-item" data-type="fixed">
<span class="change-badge badge-fixed">Fixed</span>
<span class="change-text">Search results not updating when filters were cleared</span>
</li>
<li class="change-item" data-type="fixed">
<span class="change-badge badge-fixed">Fixed</span>
<span class="change-text">File upload progress bar stalling at 99% on large files</span>
</li>
</ul>
</div>
</div>
<!-- v2.8.0 -->
<div class="timeline-entry" data-version="2.8.0" data-expanded="false">
<div class="timeline-node"></div>
<div class="timeline-card collapsed">
<div class="version-header">
<div class="version-info">
<span class="version-badge">v2.8.0</span>
<span class="version-date">November 18, 2025</span>
</div>
<button class="expand-btn" aria-label="Expand version">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</button>
</div>
<ul class="change-list">
<li class="change-item" data-type="added">
<span class="change-badge badge-added">Added</span>
<span class="change-text">Email digest option for weekly activity summaries</span>
</li>
<li class="change-item" data-type="changed">
<span class="change-badge badge-changed">Changed</span>
<span class="change-text">Improved table component with resizable columns and sticky headers</span>
</li>
<li class="change-item" data-type="fixed">
<span class="change-badge badge-fixed">Fixed</span>
<span class="change-text">Dropdown menus clipped inside modal dialogs</span>
</li>
<li class="change-item" data-type="fixed">
<span class="change-badge badge-fixed">Fixed</span>
<span class="change-text">Pagination resetting when returning from detail view</span>
</li>
<li class="change-item" data-type="fixed">
<span class="change-badge badge-fixed">Fixed</span>
<span class="change-text">Avatar upload failing for HEIC image format on Safari</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Changelog Page
A clean changelog page displaying version history with categorized changes. Each release shows added, changed, fixed, and removed items with color-coded badges.
Features
- Version timeline — vertical timeline with version badges and dates
- Change categories — Added (green), Changed (blue), Fixed (yellow), Removed (red) badges
- Filter by type — toggle to show only specific change types
- Search — filter versions by keyword
- Expandable entries — older versions collapsed by default
When to use it
- Product changelog
- Release notes page
- Open source project updates