beta
Settings — <mv-settings>
Settings page: section navigation with scroll tracking, profile (photo preview, validated fields), notification switches, security, an “unsaved changes” bar and a danger zone with type-to-confirm.
| Category | Blocks |
|---|---|
| Type | Web Component (<mv-settings>) |
| Status | beta |
| Also installs | avatar, button, checkbox, dialog, field, input, switch |
| Keywords | settings, preferences, profile, account, form, danger-zone, scrollspy |
When to use
- An app needs an account settings page with section navigation that tracks scroll
- Users edit profile, notifications and security, and must be warned about unsaved changes
- Deleting an account or workspace should require typing a confirmation
Avoid when
- The screen is about signing in, signing up or resetting a password → use Auth instead
- Only one or two preferences are edited; a dialog keeps the user in context → use Dialog instead
Install
node scripts/add.mjs settings --out ./src/marvelousAI agent with the Marvelous UI MCP server: install_components({ slugs: ["settings"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/settings/settings.js, components/settings/settings.css, components/avatar/avatar.css, components/avatar/avatar.js, components/button/button.css, components/checkbox/checkbox.css, components/dialog/dialog.js, components/dialog/dialog.css, components/field/field.css, components/input/input.css, components/switch/switch.css.
Usage
Canonical markup — start from it and customize with attributes, data-* and CSS variables:
<mv-settings id="st-demo" style="height:720px;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);overflow:hidden">
<div class="mv-settings-layout">
<nav class="mv-settings-nav" aria-label="Settings sections">
<p class="mv-settings-nav-title">Settings</p>
<ul>
<li><a href="#st-demo-profile"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>Profile</a></li>
<li><a href="#st-demo-notifs"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>Notifications</a></li>
<li><a href="#st-demo-security"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>Security</a></li>
<li><a href="#st-demo-danger" data-tone="danger"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0zM12 9v4M12 17h.01"/></svg>Danger zone</a></li>
</ul>
</nav>
<main class="mv-settings-main">
<div class="mv-settings-inner">
<header class="mv-settings-header">
<h1>Account settings</h1>
<p>Manage your profile, notifications and security for the Nova Studio workspace.</p>
</header>
<form class="mv-settings-form" novalidate>
<section class="mv-settings-section" id="st-demo-profile">
<div class="mv-settings-section-head">
<h2>Profile</h2>
<p>This information is visible to members of your workspace.</p>
</div>
<div class="mv-settings-card">
<div class="mv-settings-avatar">
<span class="mv-avatar" data-settings-avatar><img alt="Profile photo of Amara Okafor" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%2399f6e4'/%3E%3Cstop offset='1' stop-color='%230f766e'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' fill='url%28%23g%29'/%3E%3Cpath d='M10 66c1-12 10-19 22-19s21 7 22 19z' fill='%23f8fafc'/%3E%3Crect x='27' y='36' width='10' height='12' rx='4' fill='%23e7b590'/%3E%3Ccircle cx='32' cy='27' r='11' fill='%23e7b590'/%3E%3Cpath d='M20.5 26c0-8 5-13 11.5-13s11.5 5 11.5 13c-2-4-6-6.5-11.5-6.5S22.5 22 20.5 26z' fill='%237c2d12'/%3E%3C/svg%3E"><span class="mv-avatar-fallback" aria-hidden="true">AO</span></span>
<div class="mv-settings-avatar-text">
<div class="mv-settings-avatar-actions">
<label class="mv-button mv-settings-file" data-variant="outline" data-size="sm">Change photo<input type="file" name="avatar" accept="image/*" data-settings-avatar-input aria-label="Change profile photo"></label>
<button class="mv-button" type="button" data-variant="ghost" data-size="sm">Remove</button>
</div>
<small>JPG, PNG or GIF · 2 MB max</small>
</div>
</div>
<div class="mv-field-group">
<div class="mv-field-row">
<div class="mv-field">
<label class="mv-label" for="st-demo-first" data-required>First name</label>
<input class="mv-input" id="st-demo-first" name="firstName" value="Amara" required autocomplete="given-name">
</div>
<div class="mv-field">
<label class="mv-label" for="st-demo-last" data-required>Last name</label>
<input class="mv-input" id="st-demo-last" name="lastName" value="Okafor" required autocomplete="family-name">
</div>
</div>
<div class="mv-field">
<label class="mv-label" for="st-demo-email" data-required>Email address</label>
<input class="mv-input" id="st-demo-email" name="email" type="email" value="[email protected]" required autocomplete="email" aria-describedby="st-demo-email-desc">
<p class="mv-field-description" id="st-demo-email-desc">Used for sign-in and notifications.</p>
</div>
<div class="mv-field">
<label class="mv-label" for="st-demo-handle">Public handle</label>
<div class="mv-input-group">
<label class="mv-input-group-addon" data-variant="filled" for="st-demo-handle">nova.app/</label>
<input class="mv-input" id="st-demo-handle" name="handle" value="amara-okafor" pattern="[a-z0-9\-]+">
</div>
</div>
<div class="mv-field">
<label class="mv-label" for="st-demo-bio" data-optional="optional">Bio</label>
<textarea class="mv-textarea" id="st-demo-bio" name="bio" style="--mv-textarea-min-rows:2">Art director with 12 years of experience across London and Toronto.</textarea>
</div>
</div>
</div>
</section>
<section class="mv-settings-section" id="st-demo-notifs">
<div class="mv-settings-section-head">
<h2>Notifications</h2>
<p>Choose what’s worth interrupting you for.</p>
</div>
<div class="mv-settings-card">
<label class="mv-choice" data-control="end">
<input type="checkbox" role="switch" class="mv-switch" name="notifMentions" checked>
<span class="mv-choice-text"><span class="mv-choice-title">Mentions and replies</span><span class="mv-choice-description">When someone mentions you in a comment.</span></span>
</label>
<label class="mv-choice" data-control="end">
<input type="checkbox" role="switch" class="mv-switch" name="notifOrders" checked>
<span class="mv-choice-text"><span class="mv-choice-title">New orders</span><span class="mv-choice-description">One notification per paid order.</span></span>
</label>
<label class="mv-choice" data-control="end">
<input type="checkbox" role="switch" class="mv-switch" name="notifMarketing">
<span class="mv-choice-text"><span class="mv-choice-title">Product updates</span><span class="mv-choice-description">One newsletter a month, never more.</span></span>
</label>
<div class="mv-settings-row">
<div><label class="mv-settings-row-title" for="st-demo-digest">Email digest</label><span class="mv-settings-row-description">A recap of workspace activity.</span></div>
<select class="mv-select" id="st-demo-digest" name="digest" data-size="sm" data-native>
<option value="daily">Daily</option>
<option value="weekly" selected>Weekly</option>
<option value="never">Never</option>
</select>
</div>
</div>
</section>
<section class="mv-settings-section" id="st-demo-security">
<div class="mv-settings-section-head">
<h2>Security</h2>
<p>Protect access to your account.</p>
</div>
<div class="mv-settings-card">
<label class="mv-choice" data-control="end">
<input type="checkbox" role="switch" class="mv-switch" name="twoFactor" checked>
<span class="mv-choice-text"><span class="mv-choice-title">Two-factor authentication</span><span class="mv-choice-description">A one-time code is required on every new sign-in.</span></span>
</label>
<div class="mv-settings-row">
<div><span class="mv-settings-row-title">Password</span><span class="mv-settings-row-description">Changed 3 months ago.</span></div>
<button class="mv-button" type="button" data-variant="outline" data-size="sm">Change</button>
</div>
<div class="mv-settings-row">
<div><span class="mv-settings-row-title">MacBook Pro · London <span class="mv-badge" data-variant="success" data-size="sm">This device</span></span><span class="mv-settings-row-description">Chrome · active now</span></div>
</div>
<div class="mv-settings-row">
<div><span class="mv-settings-row-title">iPhone 15 · New York</span><span class="mv-settings-row-description">Safari · 2 days ago</span></div>
<button class="mv-button" type="button" data-variant="ghost" data-size="sm">Sign out</button>
</div>
</div>
</section>
<div class="mv-settings-savebar" role="region" aria-label="Pending changes">
<span class="mv-settings-savebar-status" role="status" data-dirty="Unsaved changes" data-saved="Changes saved">Unsaved changes</span>
<button class="mv-button" type="reset" data-variant="ghost" data-size="sm">Cancel</button>
<button class="mv-button" type="submit" data-size="sm">Save</button>
</div>
</form>
<section class="mv-settings-section" id="st-demo-danger" data-tone="danger">
<div class="mv-settings-section-head">
<h2>Danger zone</h2>
<p>Irreversible actions on the workspace.</p>
</div>
<div class="mv-settings-card mv-settings-danger">
<div class="mv-settings-row">
<div><span class="mv-settings-row-title">Transfer ownership</span><span class="mv-settings-row-description">Hand the workspace over to another admin.</span></div>
<button class="mv-button" type="button" data-variant="outline" data-size="sm">Transfer</button>
</div>
<div class="mv-settings-row">
<div><span class="mv-settings-row-title">Delete workspace</span><span class="mv-settings-row-description">Projects, files and invoices will be permanently erased.</span></div>
<mv-dialog persistent>
<button class="mv-button" type="button" data-variant="destructive" data-size="sm" data-mv-open>Delete…</button>
<dialog style="--mv-dialog-width:28rem">
<header class="mv-dialog-header">
<h2 class="mv-dialog-title">Delete “Nova Studio”?</h2>
<p class="mv-dialog-description">All 24 projects, 1.2 GB of files and the billing history will be deleted for all 12 members. This can’t be undone.</p>
</header>
<div class="mv-field" style="margin-bottom:1.25rem">
<label class="mv-label" for="st-demo-confirm">To confirm, type <span class="mv-settings-confirm-code">nova-studio</span></label>
<input class="mv-input" id="st-demo-confirm" data-settings-confirm data-expected="nova-studio" autocomplete="off" spellcheck="false">
</div>
<div class="mv-dialog-footer">
<button class="mv-button" type="button" data-variant="outline" data-mv-close="cancel">Cancel</button>
<button class="mv-button" type="button" data-variant="destructive" data-mv-close="delete" data-settings-confirm-action disabled>Delete permanently</button>
</div>
</dialog>
</mv-dialog>
</div>
</div>
</section>
</div>
</main>
</div>
</mv-settings>
<script type="module">
const settings = document.getElementById("st-demo");
settings?.addEventListener("mv-save", (e) => { e.preventDefault(); setTimeout(e.detail.done, 700); });
</script>API
Methods
| Name | Description |
|---|---|
markSaved() | Treats the current form state as saved (hides the bar). |
Events
| Name | Description |
|---|---|
mv-save | Valid form submitted. detail: { data, form, done }. Call preventDefault() then done() for an async save. |
mv-confirm | Danger-zone action confirmed. detail: { action } (the button’s data-mv-close value). |
Content structure
| Name | Description |
|---|---|
.mv-settings-layout > .mv-settings-nav + .mv-settings-main | Navigation (#anchor links) and scrolling content. |
.mv-settings-section | Section targeted by a link; its h2 names it and receives focus on navigation. data-tone="danger" for the danger zone. |
form.mv-settings-form | Tracked form: any change shows .mv-settings-savebar (reset = cancel, submit = save). |
[data-settings-avatar] + input[data-settings-avatar-input] | Local preview of the chosen photo (object URL, no network). |
input[data-settings-confirm][data-expected] | Confirmation input: enables [data-settings-confirm-action] when it matches. |
CSS classes
| Name | Description |
|---|---|
mv-settings-nav-title / -inner / -header | Navigation title, content column, page header. |
mv-settings-section-head / -card | Section title + description, card with divided rows. |
mv-settings-row / -row-title / -row-description | Label row with a trailing control. |
mv-settings-avatar / -avatar-text / -avatar-actions / -file | Profile photo row; -file turns a label into a file button. |
mv-settings-savebar / -savebar-status | Sticky floating bar (data-state dirty | saved). |
mv-settings-danger / -confirm-code | Danger-tinted card, code to type back. |
Accessibility
Named navigation, current link marked aria-current; navigating moves focus to the section heading (tabindex=-1). Sections are named by their heading. Native role=switch toggles. Status bar uses role=status. Deletion: alertdialog (persistent), button disabled until the input matches. Smooth scrolling is disabled with reduced motion.