- 12 minutes to read

Saved Views: UI/UX Patterns

New .7.x

This page documents the user interface and user experience patterns for the Saved Views feature in Mapify, including dropdown design, keyboard shortcuts, accessibility requirements, loading states, error handling, and mobile behavior.

Back to Saved Views


The Saved Views dropdown is the primary interface for accessing and managing saved views.

Desktop location:

[  Filters ]  [  Saved Views ▾ ]  [  Zoom In ]  [  Zoom Out ]

Mobile/Tablet: Hamburger menu → "Saved Views" submenu

Badge indicator: Displays count: "Saved Views (12)"

When the user clicks Saved Views:

  1. Opens dropdown panel below button (desktop) or full-screen modal (mobile)
  2. Content order: search box → favorites → personal views → shared views → action footer
  3. Dropdown closes when: view is selected, click outside, or Esc pressed
<nav aria-label="Saved Views Menu">
  <button
    id="saved-views-trigger"
    aria-haspopup="true"
    aria-expanded="false"
    aria-label="Open saved views menu. Currently 12 saved views available.">
    <i class="fas fa-bookmark" aria-hidden="true"></i>
    Saved Views (12)
  </button>

  <div id="saved-views-dropdown" role="menu" aria-labelledby="saved-views-trigger" hidden>

    <div class="search-box">
      <label for="view-search" class="visually-hidden">Search saved views</label>
      <input type="text" id="view-search" placeholder="Search views..."
        aria-describedby="search-help" autocomplete="off">
      <span id="search-help" class="visually-hidden">Type to filter saved views by name or tag</span>
    </div>

    <section aria-labelledby="favorites-heading">
      <h3 id="favorites-heading">
        <i class="fas fa-star" aria-hidden="true"></i> Favorites
      </h3>
      <ul role="menu">
        <li role="menuitem">
          <button class="view-item" data-view-id="a3f8d7c2-..."
            aria-label="Load favorite view: GDPR Compliance Audit Q1 2026">
            <i class="fas fa-bookmark" aria-hidden="true"></i>
            <span class="view-name">GDPR Compliance Audit Q1 2026</span>
            <span class="view-meta">Created: 2026-01-15</span>
          </button>
        </li>
      </ul>
    </section>

    <section aria-labelledby="personal-heading">
      <h3 id="personal-heading">
        <i class="fas fa-user" aria-hidden="true"></i> Personal Views
      </h3>
      <ul role="menu">
        <li role="menuitem">
          <button class="view-item" data-view-id="...">
            <i class="fas fa-bookmark" aria-hidden="true"></i>
            <span class="view-name">JD - SAP Analysis - 2026-01</span>
          </button>
        </li>
      </ul>
    </section>

    <section aria-labelledby="shared-heading">
      <h3 id="shared-heading">
        <i class="fas fa-users" aria-hidden="true"></i> Shared Views
      </h3>
      <ul role="menu">
        <li role="menuitem">
          <button class="view-item" data-view-id="...">
            <i class="fas fa-bookmark" aria-hidden="true"></i>
            <span class="view-name">Executive - Monthly Dashboard - 2026-01</span>
            <span class="view-owner">by admin@nodinite.com</span>
          </button>
        </li>
      </ul>
    </section>

    <footer class="dropdown-footer">
      <button class="btn-primary" aria-label="Create new saved view from current graph state">
        <i class="fas fa-plus" aria-hidden="true"></i> Create New View
      </button>
      <button class="btn-secondary" aria-label="Open saved views management panel">
        <i class="fas fa-cog" aria-hidden="true"></i> Manage All Views
      </button>
    </footer>
  </div>
</nav>

Desktop Layout Mockup

┌─────────────────────────────────────────────────────────────────────┐
│  [ ≡ Menu ] [ 🔍 Search ] [ ⚙ Filters ] [ 📑 Saved Views ▾ ]       │
│                                         └─────────────────┐         │
│  ┌──────────────────────────────────────────────────────┐ │         │
│  │ 📑 Saved Views (12)                                  │ │         │
│  ├──────────────────────────────────────────────────────┤ │         │
│  │ 🔍 [Search views by name or tag...]                  │ │         │
│  ├──────────────────────────────────────────────────────┤ │         │
│  │ ⭐ FAVORITES                                         │ │         │
│  │   📌 GDPR Compliance Audit Q1 2026                   │ │         │
│  │      👤 admin@nodinite.com  · Created: 2026-01-15    │ │         │
│  │   📌 Executive - Monthly Dashboard - 2026-01         │ │         │
│  ├──────────────────────────────────────────────────────┤ │         │
│  │ 👤 PERSONAL VIEWS (5)                                │ │         │
│  │   📄 JD - SAP Analysis - 2026-01                     │ │         │
│  │   📄 MK - Quick Debug - v2                           │ │         │
│  │   ... [Show 3 more]                                  │ │         │
│  ├──────────────────────────────────────────────────────┤ │         │
│  │ 👥 SHARED VIEWS (5)                                  │ │         │
│  │   📄 Compliance - SOX Audit - Q1 2026                │ │         │
│  │   📄 Onboarding - Architecture Overview - v3         │ │         │
│  │   ... [Show 3 more]                                  │ │         │
│  ├──────────────────────────────────────────────────────┤ │         │
│  │ [ ➕ Create New View ] [ ⚙ Manage All Views ]        │ │         │
│  └──────────────────────────────────────────────────────┘ │         │
└─────────────────────────────────────────────────────────────────────┘

Keyboard Shortcuts

Action Windows/Linux macOS Description
Save Current View Ctrl + S Cmd + S Opens "Save View" dialog
Open Saved Views Ctrl + O Cmd + O Opens dropdown to load existing view
Quick Switch (Next) Ctrl + Shift + . Cmd + Shift + . Cycles to next view in favorites (MRU order)
Quick Switch (Previous) Ctrl + Shift + , Cmd + Shift + , Cycles to previous view in favorites
Search Views Ctrl + K Cmd + K Opens dropdown with focus on search box
Close Dropdown Esc Esc Closes dropdown without loading a view
Navigate List Arrow Up/Down Arrow Up/Down Moves focus between items in dropdown
Load View Enter Enter Loads the currently focused view
Mark as Favorite Ctrl + F Cmd + F Toggles favorite status for focused view

Implementation notes:

  • Display shortcuts in button tooltips
  • Prevent browser default behavior (e.g., Ctrl + S must not trigger "Save Page")
  • Include shortcut in ARIA labels: aria-label="Save current view. Keyboard shortcut: Control S"
document.addEventListener('keydown', (e) => {
  if ((e.ctrlKey || e.metaKey) && e.key === 's') {
    e.preventDefault();
    openSaveViewDialog();
    announceToScreenReader('Save view dialog opened');
  }
  if ((e.ctrlKey || e.metaKey) && e.key === 'o') {
    e.preventDefault();
    toggleSavedViewsDropdown();
    announceToScreenReader('Saved views menu opened. Use arrow keys to navigate.');
  }
  if (e.key === 'Escape' && savedViewsDropdownIsOpen) {
    closeSavedViewsDropdown();
    announceToScreenReader('Saved views menu closed');
  }
});

Accessibility (WCAG 2.1 AA)

Keyboard Navigation

  • Tab key: Reaches all interactive elements — trigger → search → favorites → personal → shared → action buttons
  • No keyboard traps: Users can tab in and out of dropdown without mouse
  • Arrow keys: Navigate between view items
  • Enter/Space: Load selected view
  • Escape: Close dropdown and return focus to trigger button
function closeSavedViewsDropdown() {
  dropdown.setAttribute('hidden', '');
  triggerButton.setAttribute('aria-expanded', 'false');
  triggerButton.focus(); // Return focus to trigger
}

Screen Reader Support

<!-- Status announcements -->
<div id="status-message" role="status" aria-live="polite" aria-atomic="true" class="visually-hidden"></div>

Announcement examples:

  • Dropdown opens: "Saved views menu opened. 12 views available. Use arrow keys to navigate."
  • View selected: "GDPR Compliance Audit Q1 2026. Shared view created by admin@nodinite.com."
  • View loads: "View loaded successfully. Displaying 47 entities."
  • No results: "No saved views found matching 'test'. Try different search terms."

Color Contrast

.view-name  { color: #212529; } /* 16.1:1 on white ✓ */
.view-meta  { color: #6c757d; } /*  4.6:1 on white ✓ */

.view-item:focus {
  outline: 2px solid #0056b3;   /* 4.5:1 ✓ */
  outline-offset: 2px;
  box-shadow: 0 0 0 3px rgba(0, 86, 179, 0.2);
}

Non-color indicators: Favorites use ⭐ icon + yellow border + section heading. Shared views use 👥 icon + "by [owner]" text. Active view uses checkmark icon + bold text.


Loading States and Error Handling

Loading States

<!-- Dropdown initial load -->
<div role="status" aria-live="polite">
  <i class="fas fa-spinner fa-spin" aria-hidden="true"></i>
  <span>Loading saved views...</span>
</div>

<!-- View loading after selection -->
<div class="graph-overlay" role="status" aria-live="assertive">
  <i class="fas fa-circle-notch fa-spin" aria-hidden="true"></i>
  <h2>Loading View: GDPR Compliance Audit Q1 2026</h2>
  <progress value="60" max="100" aria-label="Loading progress: 60%">60%</progress>
</div>

<!-- Save button during operation -->
<button class="btn-primary" disabled aria-busy="true">
  <i class="fas fa-spinner fa-spin" aria-hidden="true"></i>
  Saving view...
</button>

Error Handling

Scenario 1: Network failure

<div class="alert alert-error" role="alert">
  <i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
  <strong>Error:</strong> Failed to load "GDPR Audit Q1 2026" — network connection lost.
  <button onclick="retryLoadView()">
    <i class="fas fa-redo" aria-hidden="true"></i> Retry
  </button>
</div>

Scenario 2: View deleted by another user

<div class="alert alert-warning" role="alert">
  <i class="fas fa-info-circle" aria-hidden="true"></i>
  <strong>View Not Found:</strong> "Executive Dashboard" has been deleted.
  <button onclick="refreshViewsList()">Refresh Views List</button>
</div>

Scenario 3: Concurrent edit conflict

<div class="alert alert-error" role="alert">
  <i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
  <strong>Update Conflict:</strong> This view was modified by admin@nodinite.com at 15:30.
  <button onclick="reloadViewAndMerge()">
    <i class="fas fa-sync" aria-hidden="true"></i> Reload and Retry
  </button>
  <button onclick="saveAsNewView()">
    <i class="fas fa-save" aria-hidden="true"></i> Save as New View
  </button>
</div>

Error message best practices:

  • Specific description — never "Error occurred"
  • User-actionable next steps (Retry, Refresh, Contact support)
  • Multiple recovery options (Reload, Save as New, Discard)
  • Use role="alert" for immediate screen reader announcements

User Feedback: Toasts and Spinners

Toast Notifications

<!-- Success -->
<div class="toast toast-success" role="status" aria-live="polite">
  <i class="fas fa-check-circle" aria-hidden="true"></i>
  <span><strong>Success:</strong> View "GDPR Audit Q1 2026" saved successfully.</span>
  <button class="toast-close" aria-label="Close notification">&times;</button>
</div>

<!-- Warning -->
<div class="toast toast-warning" role="status" aria-live="polite">
  <i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
  <span>Large dataset: This view may take 5–10 seconds to render.</span>
</div>

Toast behavior:

  • Auto-dismiss: 5 seconds (success/info), 10 seconds (warning/error)
  • Manual dismiss: × button or Escape
  • Position: bottom-right (desktop), top-center (mobile)

Progress Indicator (Long Operations)

<div class="progress-dialog" role="dialog" aria-labelledby="progress-title">
  <h2 id="progress-title">Loading View: GDPR Audit Q1 2026</h2>
  <p>Rendering 10,247 entities...</p>
  <div class="progress-bar" role="progressbar"
    aria-valuenow="60" aria-valuemin="0" aria-valuemax="100">
    <div class="progress-fill" style="width: 60%;"></div>
  </div>
  <span aria-live="polite">60% complete</span>
  <button onclick="cancelViewLoad()">
    <i class="fas fa-times" aria-hidden="true"></i> Cancel
  </button>
</div>

Mobile and Responsive Design

Mobile (<768px): Full-Screen Modal

<div class="saved-views-mobile-modal" role="dialog" aria-labelledby="mobile-modal-title">
  <header class="modal-header">
    <h2 id="mobile-modal-title">
      <i class="fas fa-bookmark" aria-hidden="true"></i> Saved Views
    </h2>
    <button class="btn-close" aria-label="Close saved views">
      <i class="fas fa-times" aria-hidden="true"></i>
    </button>
  </header>
  <div class="modal-body">
    <input type="search" placeholder="Search views..." class="search-mobile">
    <!-- View list sections -->
  </div>
  <footer class="modal-footer">
    <button class="btn-primary btn-block">
      <i class="fas fa-plus" aria-hidden="true"></i> Create New View
    </button>
  </footer>
</div>

Mobile mockup:

┌─────────────────────────────┐
│ 📑 Saved Views          [✖] │
├─────────────────────────────┤
│ 🔍 [Search views...]        │ ← Sticky
├─────────────────────────────┤
│ ⭐ FAVORITES            [▼] │
│   📌 GDPR Audit Q1 2026     │
│   📌 Executive Dashboard    │
├─────────────────────────────┤
│ 👤 PERSONAL VIEWS (5)   [▶] │ ← Collapsed
├─────────────────────────────┤
│ 👥 SHARED VIEWS (5)     [▶] │ ← Collapsed
├─────────────────────────────┤
│ [ ➕ Create New View ]      │
└─────────────────────────────┘

Touch requirements:

  • Minimum tap target: 44×44px
  • Minimum item spacing: 8px
  • Swipe left on item reveals Delete and Edit actions
  • Collapsible sections save screen space

Tablet (768–1024px): Side Panel

  • Panel slides in from right (position: fixed; width: 320px; height: 100vh)
  • Remains open while browsing (multi-column layout)
  • Optional preview pane on tap before loading

Responsive CSS

@media (min-width: 1025px) {
  .saved-views-dropdown { position: absolute; width: 400px; max-height: 600px; overflow-y: auto; }
}
@media (min-width: 768px) and (max-width: 1024px) {
  .saved-views-panel { position: fixed; right: 0; top: 0; width: 320px; height: 100vh;
    transform: translateX(100%); transition: transform 0.3s ease; }
  .saved-views-panel.open { transform: translateX(0); }
}
@media (max-width: 767px) {
  .saved-views-modal { position: fixed; inset: 0; z-index: 1000; background: white; }
  .view-item { min-height: 48px; padding: 12px 16px; }
}

Performance Optimization for UI

  • Lazy loading: Load 20 views initially; load more on scroll
  • Virtual scrolling: For 100+ views, render only visible items (reduces DOM)
  • Debounced search: Wait 300ms after typing stops before filtering
let searchTimeout;
searchInput.addEventListener('input', (e) => {
  clearTimeout(searchTimeout);
  searchTimeout = setTimeout(() => {
    const query = e.target.value.toLowerCase();
    filterViews(query);
    announceToScreenReader(`${filteredCount} views found matching "${query}"`);
  }, 300);
});