webdevpuneet.com

Notification Preferences — Channel Toggle Matrix UI

Notification Preferences · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="npf-card">
  <div class="npf-head">
    <h3>Notifications</h3>
    <p>Choose how you want to be notified for each type of event.</p>
  </div>

  <div class="npf-grid-head">
    <span></span>
    <span class="npf-chan" title="Email">✉<small>Email</small></span>
    <span class="npf-chan" title="Push">🔔<small>Push</small></span>
    <span class="npf-chan" title="SMS">💬<small>SMS</small></span>
  </div>

  <div class="npf-rows" id="npfRows"></div>

  <div class="npf-foot">
    <span class="npf-status" id="npfStatus">All changes saved</span>
    <button type="button" class="npf-save" id="npfSave" disabled>Save changes</button>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Event × channel matrix
A grid of event rows and Email/Push/SMS columns lets users set the whole picture at a glance.
Dirty-state save gate
Save enables only when preferences differ from the saved snapshot — toggling back disables it again.
Exact change tracking
A JSON snapshot comparison makes the dirty check precise, preventing pointless empty saves.
Three-state status line
Saved (gray), unsaved (amber), and just-saved (green) keep the user informed about their changes.
ARIA switch toggles
Each toggle is role="switch" with aria-checked and a label naming the event and channel for screen readers.
Sliding pill switches
Familiar track-and-thumb toggles with a transform-animated thumb — clear and GPU-cheap.
Data-driven matrix
Renders from an EVENTS array with per-channel prefs — add an event or channel with a data edit.
Sensible defaults
Critical notifications default on, promotional ones off — the defaults most users keep.

About this UI Snippet

Notification Preferences — Per-Event Email / Push / SMS Toggle Matrix

Screenshot of the Notification Preferences snippet rendered live

Every app with notifications eventually needs a preferences screen where users control *what* they're notified about and *how* — a grid of event types crossed with delivery channels (email, push, SMS). Done as a clear matrix, it lets people opt out of noise without turning everything off, which keeps the important notifications welcome. This snippet builds that preference matrix in plain HTML, CSS, and vanilla JavaScript: per-event toggles for each channel, a save gate that activates only when something changes, and accessible switch semantics.

A matrix, not a flat list

The layout is a grid: each row is an event type (Mentions, Comments, Security alerts, Billing…), and three columns are the delivery channels (Email, Push, SMS), each cell a toggle. This matrix is far clearer than a flat list of "Email me about X" checkboxes, because it lets the user see and set the whole picture at once — "I want security alerts on every channel but product updates on none" is two glances and a few taps. The column headers carry channel icons so the grid reads quickly even on a narrow screen.

Dirty-state save gate

The Save button stays disabled until the user actually changes something, and a status line reflects the state: "All changes saved" → "Unsaved changes" (amber) the moment a toggle differs from the saved snapshot → "✓ Saved" (green) after saving. This is implemented by comparing a JSON snapshot of the current preferences against the last-saved snapshot, so the dirty check is exact — toggling something and toggling it back correctly returns to "saved" with the button disabled again. This honest dirty-tracking is what prevents both pointless empty saves and the anxiety of not knowing whether your changes took.

Accessible toggle switches

Each toggle is a real <button> with role="switch" and aria-checked reflecting its state, plus an aria-label naming both the event and the channel ("Security alerts via SMS") — so a screen reader announces exactly what each of the eighteen toggles controls, which a grid of unlabeled switches completely fails to convey. The switch is styled as the familiar sliding pill (track + thumb) with a transform-animated thumb, so it's visually obvious and GPU-cheap.

Data-driven and snapshot-saved

The whole matrix renders from an EVENTS array, each event carrying a prefs object of channel → boolean. Adding an event type or a channel is a data edit. On save, the current preferences are captured as the new saved snapshot (and in a real app, sent to your backend) — the FAQs cover wiring that persistence. Because state lives in the data and the UI is its projection, the toggles, the dirty check, and the save all stay consistent automatically.

Sensible defaults matter

The starting preferences model good defaults — security and billing default to more channels (you want those), while product updates default to off (opt-in marketing) — because the defaults are what most users keep. A well-designed preferences screen still starts from defaults that respect attention: critical notifications on, promotional ones off, so the user only adjusts the exceptions. Swap the EVENTS and CHANNELS for your app's real notification types and delivery methods and it's ready.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA notifications matrix renders with event rows and Email / Push / SMS toggle columns; Save is disabled.
  2. 2
    Toggle a preferenceTap any switch — the status changes to "Unsaved changes" (amber) and the Save button enables.
  3. 3
    Revert a changeToggle it back to the saved value — the status returns to "All changes saved" and Save disables again.
  4. 4
    SaveClick Save — the status shows "✓ Saved" and the current state becomes the new baseline.
  5. 5
    Edit events and channelsChange the EVENTS array (label, sub, prefs) and CHANNELS to match your app's notification types.
  6. 6
    Persist to your backendIn the save handler, send the EVENTS preferences to your API instead of only snapshotting locally.

Real-world uses

Common Use Cases

Account notification settings
The standard notifications preferences page in app settings — pair with integration cards and other settings sections.
Email subscription management
Let users fine-tune which emails they receive without fully unsubscribing.
Team and workspace alerts
Configure which workspace events notify members and through which channels, beside a profile dropdown in settings.
Mobile app push settings
Control push categories alongside email, with SMS for critical alerts.
Compliance and security alerts
Ensure security notifications are on across channels while letting marketing be opt-in.
Learning preference-matrix UX
A reference for a settings matrix, dirty-state gating, and ARIA switches — compare with a settings panel for general settings.
Related: Time Duration Input
See the Time Duration Input for a related forms pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

In the save handler, send the EVENTS preferences (or a compact { eventId: { email, push, sms } } map) to your API via fetch, show a saving state, and on success update the saved snapshot to the new values so the dirty check resets. On load, fetch the user's saved preferences and initialize EVENTS from them, then snapshot that as the baseline so unmodified screens show "All changes saved".

It compares a JSON.stringify snapshot of the current preferences against the snapshot taken at the last save. Any difference means "dirty" (enable Save, show the amber status); identical means "clean" (disable Save). Because it's a full-state comparison, toggling a switch and toggling it back correctly returns to the clean state — unlike a naive "any change happened" flag that would stay dirty.

A toggle that visually slides on/off is semantically a switch, and role="switch" with aria-checked tells assistive tech to announce it as "on/off" rather than "checked/unchecked", matching its appearance and behavior. Pairing it with an aria-label that names both the event and the channel ("Billing via email") is essential here, since a bare switch in a grid gives a screen-reader user no context about what it controls.

Default critical and transactional notifications on (security alerts, billing, mentions) since users expect and need them, and default promotional or low-urgency ones off (product updates, marketing) as opt-in. Most users keep the defaults, so respecting their attention by not pre-enabling noise both improves the experience and aligns with consent best practices for marketing channels.

In React, hold the preferences in useState and compute the dirty flag by comparing to a saved ref, rendering toggles from the data; in Vue, use a reactive object with a computed dirty value; in Angular, use a component model with a getter. The toggle, snapshot-compare, and ARIA logic port unchanged — only the per-toggle re-render and the save API call move into the framework.