Pages Medium
Settings Page
A tabbed settings page with profile editing, notification preferences, security options, and appearance controls. Pure vanilla JS and CSS.
Open in Lab
MCP
vanilla-js css
Targets: JS HTML
Code
/* ── Reset & Base ──────────────────────────────── */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--accent: #6366f1;
--accent-light: #e0e7ff;
--bg: #ffffff;
--bg-secondary: #f9fafb;
--text: #111827;
--text-secondary: #6b7280;
--border: #e5e7eb;
--border-focus: var(--accent);
--danger: #ef4444;
--success: #22c55e;
--toggle-bg: #d1d5db;
--toggle-active: #22c55e;
--radius: 8px;
--radius-lg: 12px;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
--font-size: 16px;
}
html[data-theme="dark"] {
--bg: #111827;
--bg-secondary: #1f2937;
--text: #f9fafb;
--text-secondary: #9ca3af;
--border: #374151;
--accent-light: #312e81;
--toggle-bg: #4b5563;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
sans-serif;
font-size: var(--font-size);
line-height: 1.6;
color: var(--text);
background: var(--bg-secondary);
min-height: 100vh;
}
/* ── Top Bar ──────────────────────────────────── */
.topbar {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 24px;
background: var(--bg);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 100;
}
.topbar-back {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border: none;
border-radius: var(--radius);
background: transparent;
color: var(--text);
cursor: pointer;
transition: background 0.15s;
}
.topbar-back:hover {
background: var(--bg-secondary);
}
.topbar-title {
font-size: 18px;
font-weight: 600;
color: var(--text);
}
/* ── Layout ───────────────────────────────────── */
.settings-layout {
display: flex;
max-width: 1100px;
margin: 0 auto;
min-height: calc(100vh - 61px);
}
/* ── Sidebar ──────────────────────────────────── */
.sidebar {
width: 240px;
flex-shrink: 0;
background: var(--bg);
border-right: 1px solid var(--border);
padding: 16px 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.sidebar-tab {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 10px 20px;
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 14px;
font-weight: 500;
cursor: pointer;
text-align: left;
border-left: 3px solid transparent;
transition: all 0.15s;
font-family: inherit;
}
.sidebar-tab:hover {
background: var(--bg-secondary);
color: var(--text);
}
.sidebar-tab.active {
color: var(--accent);
background: var(--accent-light);
border-left-color: var(--accent);
}
.sidebar-tab svg {
flex-shrink: 0;
}
/* ── Content ──────────────────────────────────── */
.content {
flex: 1;
padding: 32px 40px;
max-width: 680px;
background: var(--bg);
}
.panel {
display: none;
}
.panel.active {
display: block;
}
.panel-title {
font-size: 24px;
font-weight: 700;
color: var(--text);
margin-bottom: 4px;
}
.panel-desc {
color: var(--text-secondary);
font-size: 14px;
margin-bottom: 32px;
}
/* ── Section Blocks ───────────────────────────── */
.section-block {
margin-bottom: 32px;
padding-bottom: 32px;
border-bottom: 1px solid var(--border);
}
.section-block:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.section-heading {
font-size: 16px;
font-weight: 600;
color: var(--text);
margin-bottom: 16px;
}
/* ── Avatar ───────────────────────────────────── */
.avatar-section {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 32px;
padding-bottom: 32px;
border-bottom: 1px solid var(--border);
}
.avatar {
position: relative;
width: 96px;
height: 96px;
border-radius: 50%;
background: var(--accent);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
overflow: hidden;
flex-shrink: 0;
}
.avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-initials {
font-size: 32px;
font-weight: 700;
color: #fff;
}
.avatar-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
opacity: 0;
transition: opacity 0.2s;
border-radius: 50%;
}
.avatar:hover .avatar-overlay {
opacity: 1;
}
.avatar-name {
font-size: 16px;
font-weight: 600;
color: var(--text);
}
.avatar-hint {
font-size: 13px;
color: var(--text-secondary);
margin-top: 2px;
}
/* ── Forms ────────────────────────────────────── */
.form {
display: flex;
flex-direction: column;
gap: 20px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.form-group label {
font-size: 14px;
font-weight: 500;
color: var(--text);
}
.required {
color: var(--danger);
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 10px 14px;
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: 14px;
font-family: inherit;
color: var(--text);
background: var(--bg);
transition: border-color 0.15s, box-shadow 0.15s;
outline: none;
}
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
}
.form-group input.error,
.form-group textarea.error {
border-color: var(--danger);
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.form-error {
font-size: 12px;
color: var(--danger);
min-height: 16px;
}
.form-actions {
display: flex;
gap: 12px;
padding-top: 8px;
}
/* ── Buttons ──────────────────────────────────── */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
border: none;
border-radius: var(--radius);
font-size: 14px;
font-weight: 500;
font-family: inherit;
cursor: pointer;
transition: all 0.15s;
text-decoration: none;
}
.btn-primary {
background: var(--accent);
color: #fff;
}
.btn-primary:hover {
opacity: 0.9;
box-shadow: var(--shadow);
}
.btn-secondary {
background: transparent;
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--bg-secondary);
}
.btn-sm {
padding: 6px 14px;
font-size: 13px;
}
.btn-danger-sm {
padding: 6px 14px;
font-size: 13px;
border: none;
border-radius: var(--radius);
background: transparent;
color: var(--danger);
font-weight: 500;
cursor: pointer;
font-family: inherit;
transition: background 0.15s;
}
.btn-danger-sm:hover {
background: rgba(239, 68, 68, 0.08);
}
/* ── Toggle Switches ──────────────────────────── */
.toggle-list {
display: flex;
flex-direction: column;
}
.toggle-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
border-bottom: 1px solid var(--border);
}
.toggle-item:last-child {
border-bottom: none;
}
.toggle-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.toggle-label {
font-size: 14px;
font-weight: 500;
color: var(--text);
}
.toggle-desc {
font-size: 13px;
color: var(--text-secondary);
}
.toggle {
position: relative;
width: 48px;
height: 26px;
border: none;
border-radius: 13px;
background: var(--toggle-bg);
cursor: pointer;
padding: 0;
flex-shrink: 0;
transition: background 0.2s;
}
.toggle.active {
background: var(--toggle-active);
}
.toggle-knob {
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
border-radius: 50%;
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
transition: transform 0.2s;
}
.toggle.active .toggle-knob {
transform: translateX(22px);
}
/* ── 2FA ──────────────────────────────────────── */
.tfa-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 20px;
}
.tfa-info {
flex: 1;
}
.tfa-status {
font-size: 14px;
color: var(--text);
margin-bottom: 4px;
}
.tfa-desc {
font-size: 13px;
color: var(--text-secondary);
}
.tfa-qr {
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 24px;
border: 1px dashed var(--border);
border-radius: var(--radius-lg);
background: var(--bg-secondary);
}
.qr-hint {
font-size: 13px;
color: var(--text-secondary);
}
/* ── Sessions ─────────────────────────────────── */
.sessions-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
}
.session-item {
display: flex;
align-items: center;
gap: 14px;
padding: 14px 16px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--bg);
}
.session-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius);
background: var(--bg-secondary);
color: var(--text-secondary);
flex-shrink: 0;
}
.session-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
}
.session-device {
font-size: 14px;
font-weight: 500;
color: var(--text);
}
.session-meta {
font-size: 12px;
color: var(--text-secondary);
}
.session-badge {
font-size: 12px;
font-weight: 500;
padding: 4px 10px;
border-radius: 99px;
}
.session-badge.current {
background: rgba(34, 197, 94, 0.1);
color: #16a34a;
}
/* ── Theme Cards ──────────────────────────────── */
.theme-cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.theme-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 12px;
border: 2px solid var(--border);
border-radius: var(--radius-lg);
background: var(--bg);
cursor: pointer;
transition: border-color 0.15s;
font-family: inherit;
}
.theme-card:hover {
border-color: var(--text-secondary);
}
.theme-card.selected {
border-color: var(--accent);
}
.theme-card-label {
font-size: 13px;
font-weight: 500;
color: var(--text);
}
.theme-preview {
width: 100%;
height: 72px;
border-radius: 6px;
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.theme-preview-light {
background: #f9fafb;
}
.theme-preview-dark {
background: #1f2937;
}
.theme-preview-system {
background: linear-gradient(135deg, #f9fafb 50%, #1f2937 50%);
}
.tp-bar {
width: 40%;
height: 6px;
border-radius: 3px;
background: currentColor;
opacity: 0.3;
}
.tp-line {
height: 4px;
border-radius: 2px;
background: currentColor;
opacity: 0.15;
}
.tp-line-w60 {
width: 60%;
}
.tp-line-w80 {
width: 80%;
}
.tp-line-w40 {
width: 40%;
}
.theme-preview-light {
color: #111827;
}
.theme-preview-dark {
color: #f9fafb;
}
.theme-preview-system {
color: #6b7280;
}
/* ── Color Picker ─────────────────────────────── */
.color-picker {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.color-circle {
width: 32px;
height: 32px;
border-radius: 50%;
border: 3px solid transparent;
cursor: pointer;
transition: transform 0.15s, border-color 0.15s;
padding: 0;
}
.color-circle:hover {
transform: scale(1.15);
}
.color-circle.selected {
border-color: var(--text);
box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--text);
}
/* ── Font Size Slider ─────────────────────────── */
.font-size-control {
display: flex;
align-items: center;
gap: 16px;
}
.fs-label {
font-size: 13px;
color: var(--text-secondary);
min-width: 40px;
}
.fs-label:last-child {
text-align: right;
}
input[type="range"] {
flex: 1;
height: 6px;
-webkit-appearance: none;
appearance: none;
background: var(--border);
border-radius: 3px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--accent);
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
transition: transform 0.15s;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.15);
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--accent);
cursor: pointer;
border: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.fs-preview {
margin-top: 12px;
font-size: 14px;
color: var(--text-secondary);
}
/* ── Billing ──────────────────────────────────── */
.plan-card {
padding: 20px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--bg);
}
.plan-info {
display: flex;
align-items: baseline;
gap: 12px;
margin-bottom: 8px;
}
.plan-name {
font-size: 18px;
font-weight: 700;
color: var(--text);
}
.plan-price {
font-size: 24px;
font-weight: 700;
color: var(--accent);
}
.plan-period {
font-size: 14px;
font-weight: 400;
color: var(--text-secondary);
}
.plan-desc {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 16px;
}
.payment-card {
display: flex;
align-items: center;
gap: 14px;
padding: 16px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
}
.payment-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
}
.payment-type {
font-size: 14px;
font-weight: 500;
color: var(--text);
}
.payment-expiry {
font-size: 13px;
color: var(--text-secondary);
}
.table-wrapper {
overflow-x: auto;
}
.billing-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.billing-table th {
text-align: left;
font-weight: 500;
color: var(--text-secondary);
padding: 10px 12px;
border-bottom: 2px solid var(--border);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.billing-table td {
padding: 12px;
border-bottom: 1px solid var(--border);
color: var(--text);
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 99px;
font-size: 12px;
font-weight: 500;
}
.badge-success {
background: rgba(34, 197, 94, 0.1);
color: #16a34a;
}
/* ── Toast ────────────────────────────────────── */
.toast {
position: fixed;
bottom: 24px;
right: 24px;
padding: 14px 24px;
background: #111827;
color: #fff;
font-size: 14px;
font-weight: 500;
border-radius: var(--radius);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
transform: translateY(100px);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
z-index: 1000;
pointer-events: none;
}
.toast.visible {
transform: translateY(0);
opacity: 1;
}
/* ── Responsive ───────────────────────────────── */
@media (max-width: 768px) {
.settings-layout {
flex-direction: column;
}
.sidebar {
width: 100%;
flex-direction: row;
border-right: none;
border-bottom: 1px solid var(--border);
padding: 8px 12px;
gap: 4px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.sidebar-tab {
border-left: none;
border-bottom: 3px solid transparent;
padding: 8px 14px;
white-space: nowrap;
border-radius: 99px;
font-size: 13px;
}
.sidebar-tab.active {
border-left-color: transparent;
border-bottom-color: var(--accent);
border-radius: 99px;
}
.sidebar-tab svg {
display: none;
}
.content {
padding: 24px 16px;
max-width: 100%;
}
.form-row {
grid-template-columns: 1fr;
}
.theme-cards {
grid-template-columns: 1fr;
}
.avatar-section {
flex-direction: column;
text-align: center;
}
.session-item {
flex-wrap: wrap;
}
.tfa-row {
flex-direction: column;
}
}(function () {
"use strict";
// ── Tab Switching ──────────────────────────────
const tabs = document.querySelectorAll(".sidebar-tab");
const panels = document.querySelectorAll(".panel");
tabs.forEach(function (tab) {
tab.addEventListener("click", function () {
const target = tab.getAttribute("data-tab");
tabs.forEach(function (t) {
t.classList.remove("active");
t.setAttribute("aria-selected", "false");
});
tab.classList.add("active");
tab.setAttribute("aria-selected", "true");
panels.forEach(function (p) {
p.classList.remove("active");
});
var panel = document.getElementById("panel-" + target);
if (panel) {
panel.classList.add("active");
}
});
});
// ── Toggle Switches ───────────────────────────
document.querySelectorAll(".toggle").forEach(function (toggle) {
toggle.addEventListener("click", function () {
var isActive = toggle.classList.toggle("active");
toggle.setAttribute("aria-checked", String(isActive));
});
});
// ── 2FA Toggle ─────────────────────────────────
var tfaToggle = document.getElementById("tfa-toggle");
var tfaQr = document.getElementById("tfa-qr");
if (tfaToggle && tfaQr) {
tfaToggle.addEventListener("click", function () {
var enabled = tfaToggle.classList.contains("active");
var statusEl = document.querySelector(".tfa-status strong");
if (enabled) {
tfaQr.hidden = false;
if (statusEl) statusEl.textContent = "Enabled";
} else {
tfaQr.hidden = true;
if (statusEl) statusEl.textContent = "Disabled";
}
});
}
// ── Theme Selector ─────────────────────────────
var themeCards = document.querySelectorAll(".theme-card");
themeCards.forEach(function (card) {
card.addEventListener("click", function () {
themeCards.forEach(function (c) {
c.classList.remove("selected");
});
card.classList.add("selected");
var theme = card.getAttribute("data-theme");
if (theme === "dark") {
document.documentElement.setAttribute("data-theme", "dark");
} else if (theme === "light") {
document.documentElement.setAttribute("data-theme", "light");
} else {
var prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.setAttribute("data-theme", prefersDark ? "dark" : "light");
}
});
});
// ── Color Picker ───────────────────────────────
var colorCircles = document.querySelectorAll(".color-circle");
colorCircles.forEach(function (circle) {
circle.addEventListener("click", function () {
colorCircles.forEach(function (c) {
c.classList.remove("selected");
});
circle.classList.add("selected");
var color = circle.getAttribute("data-color");
document.documentElement.style.setProperty("--accent", color);
// Derive a lighter version for accent-light
var r = parseInt(color.slice(1, 3), 16);
var g = parseInt(color.slice(3, 5), 16);
var b = parseInt(color.slice(5, 7), 16);
var light = "rgba(" + r + "," + g + "," + b + ",0.1)";
document.documentElement.style.setProperty("--accent-light", light);
});
});
// ── Font Size Slider ───────────────────────────
var slider = document.getElementById("font-size-slider");
var fsValue = document.getElementById("fs-value");
var fsPreview = document.getElementById("fs-preview");
if (slider) {
slider.addEventListener("input", function () {
var size = slider.value;
document.documentElement.style.setProperty("--font-size", size + "px");
if (fsValue) fsValue.textContent = size;
if (fsPreview) fsPreview.style.fontSize = size + "px";
});
}
// ── Avatar Upload ──────────────────────────────
var avatar = document.getElementById("avatar");
var avatarInput = document.getElementById("avatar-input");
if (avatar && avatarInput) {
avatar.addEventListener("click", function () {
avatarInput.click();
});
avatarInput.addEventListener("change", function () {
var file = avatarInput.files && avatarInput.files[0];
if (!file) return;
if (file.size > 2 * 1024 * 1024) {
showToast("File too large. Maximum size is 2MB.");
return;
}
var reader = new FileReader();
reader.onload = function (e) {
// Remove initials, add image
var initials = avatar.querySelector(".avatar-initials");
if (initials) initials.remove();
var existing = avatar.querySelector("img");
if (existing) existing.remove();
var img = document.createElement("img");
img.src = e.target.result;
img.alt = "Avatar";
avatar.insertBefore(img, avatar.firstChild);
showToast("Avatar updated successfully.");
};
reader.readAsDataURL(file);
});
}
// ── Profile Form Validation ────────────────────
var profileForm = document.getElementById("profile-form");
if (profileForm) {
profileForm.addEventListener("submit", function (e) {
e.preventDefault();
clearErrors(profileForm);
var valid = true;
var firstName = document.getElementById("first-name");
var lastName = document.getElementById("last-name");
var email = document.getElementById("email");
if (firstName && !firstName.value.trim()) {
showError(firstName, "first-name", "First name is required.");
valid = false;
}
if (lastName && !lastName.value.trim()) {
showError(lastName, "last-name", "Last name is required.");
valid = false;
}
if (email) {
if (!email.value.trim()) {
showError(email, "email", "Email is required.");
valid = false;
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim())) {
showError(email, "email", "Please enter a valid email address.");
valid = false;
}
}
if (valid) {
showToast("Settings saved successfully.");
}
});
}
// ── Password Form Validation ───────────────────
var passwordForm = document.getElementById("password-form");
if (passwordForm) {
passwordForm.addEventListener("submit", function (e) {
e.preventDefault();
clearErrors(passwordForm);
var valid = true;
var current = document.getElementById("current-password");
var newPass = document.getElementById("new-password");
var confirm = document.getElementById("confirm-password");
if (current && !current.value) {
showError(current, "current-password", "Current password is required.");
valid = false;
}
if (newPass && !newPass.value) {
showError(newPass, "new-password", "New password is required.");
valid = false;
} else if (newPass && newPass.value.length < 8) {
showError(newPass, "new-password", "Password must be at least 8 characters.");
valid = false;
}
if (confirm && !confirm.value) {
showError(confirm, "confirm-password", "Please confirm your new password.");
valid = false;
} else if (newPass && confirm && newPass.value !== confirm.value) {
showError(confirm, "confirm-password", "Passwords do not match.");
valid = false;
}
if (valid) {
passwordForm.reset();
showToast("Password changed successfully.");
}
});
}
// ── Cancel Button ──────────────────────────────
var cancelBtn = document.getElementById("profile-cancel");
if (cancelBtn) {
cancelBtn.addEventListener("click", function () {
var form = document.getElementById("profile-form");
if (form) {
form.reset();
clearErrors(form);
}
});
}
// ── Revoke Session ─────────────────────────────
document.querySelectorAll(".btn-danger-sm").forEach(function (btn) {
btn.addEventListener("click", function () {
var item = btn.closest(".session-item");
if (item) {
item.style.transition = "opacity 0.3s, transform 0.3s";
item.style.opacity = "0";
item.style.transform = "translateX(20px)";
setTimeout(function () {
item.remove();
}, 300);
showToast("Session revoked.");
}
});
});
// ── Helpers ────────────────────────────────────
function showError(input, name, message) {
input.classList.add("error");
var errorEl = document.querySelector('[data-error="' + name + '"]');
if (errorEl) errorEl.textContent = message;
}
function clearErrors(form) {
form.querySelectorAll(".error").forEach(function (el) {
el.classList.remove("error");
});
form.querySelectorAll(".form-error").forEach(function (el) {
el.textContent = "";
});
}
var toastTimeout;
function showToast(message) {
var toast = document.getElementById("toast");
if (!toast) return;
clearTimeout(toastTimeout);
toast.textContent = message;
toast.classList.add("visible");
toastTimeout = setTimeout(function () {
toast.classList.remove("visible");
}, 3000);
}
})();<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Settings</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- Top bar -->
<header class="topbar">
<button class="topbar-back" aria-label="Go back">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 10H5M5 10L10 5M5 10L10 15"/>
</svg>
</button>
<h1 class="topbar-title">Settings</h1>
</header>
<div class="settings-layout">
<!-- Sidebar tabs -->
<nav class="sidebar" role="tablist" aria-label="Settings sections">
<button class="sidebar-tab active" role="tab" aria-selected="true" aria-controls="panel-profile" data-tab="profile">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span>Profile</span>
</button>
<button class="sidebar-tab" role="tab" aria-selected="false" aria-controls="panel-notifications" data-tab="notifications">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
<span>Notifications</span>
</button>
<button class="sidebar-tab" role="tab" aria-selected="false" aria-controls="panel-security" data-tab="security">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<span>Security</span>
</button>
<button class="sidebar-tab" role="tab" aria-selected="false" aria-controls="panel-appearance" data-tab="appearance">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
<span>Appearance</span>
</button>
<button class="sidebar-tab" role="tab" aria-selected="false" aria-controls="panel-billing" data-tab="billing">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>
<span>Billing</span>
</button>
</nav>
<!-- Content panels -->
<main class="content">
<!-- Profile -->
<section id="panel-profile" class="panel active" role="tabpanel" aria-labelledby="tab-profile">
<h2 class="panel-title">Profile</h2>
<p class="panel-desc">Manage your personal information and public profile.</p>
<div class="avatar-section">
<div class="avatar" id="avatar">
<span class="avatar-initials">JD</span>
<div class="avatar-overlay">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
</div>
<input type="file" id="avatar-input" accept="image/*" hidden />
</div>
<div class="avatar-info">
<p class="avatar-name">John Doe</p>
<p class="avatar-hint">Click avatar to upload a new photo. Max 2MB.</p>
</div>
</div>
<form id="profile-form" class="form" novalidate>
<div class="form-row">
<div class="form-group">
<label for="first-name">First Name <span class="required">*</span></label>
<input type="text" id="first-name" value="John" required />
<span class="form-error" data-error="first-name"></span>
</div>
<div class="form-group">
<label for="last-name">Last Name <span class="required">*</span></label>
<input type="text" id="last-name" value="Doe" required />
<span class="form-error" data-error="last-name"></span>
</div>
</div>
<div class="form-group">
<label for="email">Email <span class="required">*</span></label>
<input type="email" id="email" value="john.doe@example.com" required />
<span class="form-error" data-error="email"></span>
</div>
<div class="form-group">
<label for="bio">Bio</label>
<textarea id="bio" rows="4" placeholder="Tell us a little about yourself...">Full-stack developer who loves building great user experiences.</textarea>
</div>
<div class="form-group">
<label for="timezone">Timezone</label>
<select id="timezone">
<option value="UTC-12">(UTC-12:00) Baker Island</option>
<option value="UTC-8">(UTC-08:00) Pacific Time</option>
<option value="UTC-5" selected>(UTC-05:00) Eastern Time</option>
<option value="UTC+0">(UTC+00:00) London</option>
<option value="UTC+1">(UTC+01:00) Central Europe</option>
<option value="UTC+5:30">(UTC+05:30) India</option>
<option value="UTC+8">(UTC+08:00) Singapore / China</option>
<option value="UTC+9">(UTC+09:00) Tokyo</option>
</select>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save Changes</button>
<button type="button" class="btn btn-secondary" id="profile-cancel">Cancel</button>
</div>
</form>
</section>
<!-- Notifications -->
<section id="panel-notifications" class="panel" role="tabpanel" aria-labelledby="tab-notifications">
<h2 class="panel-title">Notifications</h2>
<p class="panel-desc">Choose how and when you want to be notified.</p>
<div class="toggle-list">
<div class="toggle-item">
<div class="toggle-info">
<span class="toggle-label">Email Notifications</span>
<span class="toggle-desc">Receive updates and alerts via email</span>
</div>
<button class="toggle active" role="switch" aria-checked="true" aria-label="Email Notifications">
<span class="toggle-knob"></span>
</button>
</div>
<div class="toggle-item">
<div class="toggle-info">
<span class="toggle-label">Push Notifications</span>
<span class="toggle-desc">Get real-time push notifications in your browser</span>
</div>
<button class="toggle active" role="switch" aria-checked="true" aria-label="Push Notifications">
<span class="toggle-knob"></span>
</button>
</div>
<div class="toggle-item">
<div class="toggle-info">
<span class="toggle-label">SMS Alerts</span>
<span class="toggle-desc">Receive critical alerts via text message</span>
</div>
<button class="toggle" role="switch" aria-checked="false" aria-label="SMS Alerts">
<span class="toggle-knob"></span>
</button>
</div>
<div class="toggle-item">
<div class="toggle-info">
<span class="toggle-label">Weekly Digest</span>
<span class="toggle-desc">Get a weekly summary of your activity</span>
</div>
<button class="toggle active" role="switch" aria-checked="true" aria-label="Weekly Digest">
<span class="toggle-knob"></span>
</button>
</div>
<div class="toggle-item">
<div class="toggle-info">
<span class="toggle-label">Marketing Emails</span>
<span class="toggle-desc">Receive product updates, tips, and promotions</span>
</div>
<button class="toggle" role="switch" aria-checked="false" aria-label="Marketing Emails">
<span class="toggle-knob"></span>
</button>
</div>
</div>
</section>
<!-- Security -->
<section id="panel-security" class="panel" role="tabpanel" aria-labelledby="tab-security">
<h2 class="panel-title">Security</h2>
<p class="panel-desc">Protect your account with passwords and two-factor authentication.</p>
<div class="section-block">
<h3 class="section-heading">Change Password</h3>
<form id="password-form" class="form" novalidate>
<div class="form-group">
<label for="current-password">Current Password <span class="required">*</span></label>
<input type="password" id="current-password" required />
<span class="form-error" data-error="current-password"></span>
</div>
<div class="form-group">
<label for="new-password">New Password <span class="required">*</span></label>
<input type="password" id="new-password" required />
<span class="form-error" data-error="new-password"></span>
</div>
<div class="form-group">
<label for="confirm-password">Confirm New Password <span class="required">*</span></label>
<input type="password" id="confirm-password" required />
<span class="form-error" data-error="confirm-password"></span>
</div>
<button type="submit" class="btn btn-primary">Change Password</button>
</form>
</div>
<div class="section-block">
<h3 class="section-heading">Two-Factor Authentication</h3>
<div class="tfa-row">
<div class="tfa-info">
<p class="tfa-status">Status: <strong>Disabled</strong></p>
<p class="tfa-desc">Add an extra layer of security to your account by requiring a verification code in addition to your password.</p>
</div>
<button class="toggle" role="switch" aria-checked="false" aria-label="Two-Factor Authentication" id="tfa-toggle">
<span class="toggle-knob"></span>
</button>
</div>
<div class="tfa-qr" id="tfa-qr" hidden>
<div class="qr-placeholder">
<svg width="120" height="120" viewBox="0 0 120 120" fill="none">
<rect width="120" height="120" rx="8" fill="#f3f4f6"/>
<rect x="20" y="20" width="30" height="30" rx="2" fill="#374151"/>
<rect x="70" y="20" width="30" height="30" rx="2" fill="#374151"/>
<rect x="20" y="70" width="30" height="30" rx="2" fill="#374151"/>
<rect x="55" y="55" width="10" height="10" fill="#374151"/>
<rect x="70" y="70" width="10" height="10" fill="#374151"/>
<rect x="85" y="85" width="15" height="15" rx="2" fill="#374151"/>
<rect x="70" y="85" width="10" height="10" fill="#374151"/>
<rect x="85" y="70" width="10" height="10" fill="#374151"/>
</svg>
</div>
<p class="qr-hint">Scan this QR code with your authenticator app.</p>
</div>
</div>
<div class="section-block">
<h3 class="section-heading">Active Sessions</h3>
<ul class="sessions-list">
<li class="session-item">
<div class="session-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
</div>
<div class="session-info">
<span class="session-device">MacBook Pro — Chrome</span>
<span class="session-meta">New York, US · Active now</span>
</div>
<span class="session-badge current">Current</span>
</li>
<li class="session-item">
<div class="session-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
</div>
<div class="session-info">
<span class="session-device">iPhone 15 — Safari</span>
<span class="session-meta">New York, US · 2 hours ago</span>
</div>
<button class="btn btn-danger-sm">Revoke</button>
</li>
<li class="session-item">
<div class="session-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
</div>
<div class="session-info">
<span class="session-device">Windows PC — Firefox</span>
<span class="session-meta">London, UK · 3 days ago</span>
</div>
<button class="btn btn-danger-sm">Revoke</button>
</li>
</ul>
</div>
</section>
<!-- Appearance -->
<section id="panel-appearance" class="panel" role="tabpanel" aria-labelledby="tab-appearance">
<h2 class="panel-title">Appearance</h2>
<p class="panel-desc">Customize how the app looks and feels.</p>
<div class="section-block">
<h3 class="section-heading">Theme</h3>
<div class="theme-cards">
<button class="theme-card selected" data-theme="light" aria-label="Light theme">
<div class="theme-preview theme-preview-light">
<div class="tp-bar"></div>
<div class="tp-line tp-line-w60"></div>
<div class="tp-line tp-line-w80"></div>
<div class="tp-line tp-line-w40"></div>
</div>
<span class="theme-card-label">Light</span>
</button>
<button class="theme-card" data-theme="dark" aria-label="Dark theme">
<div class="theme-preview theme-preview-dark">
<div class="tp-bar"></div>
<div class="tp-line tp-line-w60"></div>
<div class="tp-line tp-line-w80"></div>
<div class="tp-line tp-line-w40"></div>
</div>
<span class="theme-card-label">Dark</span>
</button>
<button class="theme-card" data-theme="system" aria-label="System theme">
<div class="theme-preview theme-preview-system">
<div class="tp-bar"></div>
<div class="tp-line tp-line-w60"></div>
<div class="tp-line tp-line-w80"></div>
<div class="tp-line tp-line-w40"></div>
</div>
<span class="theme-card-label">System</span>
</button>
</div>
</div>
<div class="section-block">
<h3 class="section-heading">Accent Color</h3>
<div class="color-picker">
<button class="color-circle selected" data-color="#6366f1" style="background:#6366f1" aria-label="Indigo"></button>
<button class="color-circle" data-color="#3b82f6" style="background:#3b82f6" aria-label="Blue"></button>
<button class="color-circle" data-color="#10b981" style="background:#10b981" aria-label="Emerald"></button>
<button class="color-circle" data-color="#f59e0b" style="background:#f59e0b" aria-label="Amber"></button>
<button class="color-circle" data-color="#ef4444" style="background:#ef4444" aria-label="Red"></button>
<button class="color-circle" data-color="#8b5cf6" style="background:#8b5cf6" aria-label="Violet"></button>
</div>
</div>
<div class="section-block">
<h3 class="section-heading">Font Size</h3>
<div class="font-size-control">
<span class="fs-label">Small</span>
<input type="range" id="font-size-slider" min="12" max="20" value="16" step="1" />
<span class="fs-label">Large</span>
</div>
<p class="fs-preview" id="fs-preview">Preview text at <span id="fs-value">16</span>px</p>
</div>
</section>
<!-- Billing -->
<section id="panel-billing" class="panel" role="tabpanel" aria-labelledby="tab-billing">
<h2 class="panel-title">Billing</h2>
<p class="panel-desc">Manage your subscription plan and payment methods.</p>
<div class="section-block">
<h3 class="section-heading">Current Plan</h3>
<div class="plan-card">
<div class="plan-info">
<span class="plan-name">Pro Plan</span>
<span class="plan-price">$29 <span class="plan-period">/ month</span></span>
</div>
<p class="plan-desc">Unlimited projects, priority support, advanced analytics.</p>
<button class="btn btn-secondary">Change Plan</button>
</div>
</div>
<div class="section-block">
<h3 class="section-heading">Payment Method</h3>
<div class="payment-card">
<div class="payment-icon">
<svg width="32" height="22" viewBox="0 0 32 22" fill="none">
<rect width="32" height="22" rx="4" fill="#1a1f36"/>
<circle cx="12" cy="11" r="6" fill="#eb001b" opacity="0.9"/>
<circle cx="20" cy="11" r="6" fill="#f79e1b" opacity="0.9"/>
</svg>
</div>
<div class="payment-info">
<span class="payment-type">Mastercard ending in 4829</span>
<span class="payment-expiry">Expires 09/2027</span>
</div>
<button class="btn btn-secondary btn-sm">Update</button>
</div>
</div>
<div class="section-block">
<h3 class="section-heading">Billing History</h3>
<div class="table-wrapper">
<table class="billing-table">
<thead>
<tr>
<th>Date</th>
<th>Description</th>
<th>Amount</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mar 1, 2026</td>
<td>Pro Plan — Monthly</td>
<td>$29.00</td>
<td><span class="badge badge-success">Paid</span></td>
</tr>
<tr>
<td>Feb 1, 2026</td>
<td>Pro Plan — Monthly</td>
<td>$29.00</td>
<td><span class="badge badge-success">Paid</span></td>
</tr>
<tr>
<td>Jan 1, 2026</td>
<td>Pro Plan — Monthly</td>
<td>$29.00</td>
<td><span class="badge badge-success">Paid</span></td>
</tr>
<tr>
<td>Dec 1, 2025</td>
<td>Pro Plan — Monthly</td>
<td>$29.00</td>
<td><span class="badge badge-success">Paid</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
</main>
</div>
<!-- Toast -->
<div class="toast" id="toast" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Settings Page
A complete account settings page with vertical tabs for different settings sections. Includes profile editing, notification preferences, security settings, and appearance customization.
Features
- Vertical tab navigation — Profile, Notifications, Security, Appearance, Billing
- Profile section — avatar upload, name, email, bio textarea, save button
- Notifications — toggle switches for email, push, SMS, marketing notifications
- Security — password change form, two-factor auth toggle, active sessions list
- Appearance — theme toggle (light/dark/system), accent color picker, font size slider
- Form validation — inline error messages on required fields
- Responsive — tabs become horizontal pills on mobile
When to use it
- User account settings page
- App preferences panel
- Admin configuration interface