diff --git a/src/js/generic-storage-ui.js b/src/js/generic-storage-ui.js
new file mode 100644
index 0000000..656b499
--- /dev/null
+++ b/src/js/generic-storage-ui.js
@@ -0,0 +1,264 @@
+// Generic Storage UI Utilities
+// Provides reusable patterns for list management, item selection, and linked item display
+// Used by both snippet-manager.js and dataset-manager.js
+
+/**
+ * Generic list rendering function
+ * @param {string} containerId - ID of container to render list into
+ * @param {Array} items - Array of items to render
+ * @param {Function} formatItem - Function that takes item and returns HTML string
+ * @param {Function} onSelectItem - Callback when item is selected, receives item ID
+ * @param {string} emptyMessage - Message to show when list is empty
+ */
+function renderGenericList(containerId, items, formatItem, onSelectItem, emptyMessage = 'No items found') {
+ const container = document.getElementById(containerId);
+ if (!container) return;
+
+ if (items.length === 0) {
+ container.innerHTML = `
${emptyMessage}
`;
+ return;
+ }
+
+ const html = items.map(formatItem).join('');
+ container.innerHTML = html;
+
+ // Attach click handlers to items
+ container.querySelectorAll('[data-item-id]').forEach(item => {
+ item.addEventListener('click', function() {
+ const itemId = this.dataset.itemId;
+ onSelectItem(itemId);
+ });
+ });
+}
+
+/**
+ * Generic item selector function
+ * Updates visual selection and populates details panel
+ * @param {string|number} itemId - ID of item to select
+ * @param {string} itemContainerSelector - CSS selector for item elements (e.g., '.snippet-item')
+ * @param {string} dataAttributeName - Name of data attribute holding ID (e.g., 'snippetId', 'datasetId')
+ */
+function selectGenericItem(itemId, itemContainerSelector, dataAttributeName) {
+ // Clear previous selection
+ document.querySelectorAll(itemContainerSelector).forEach(item => {
+ item.classList.remove('selected');
+ });
+
+ // Set new selection
+ const selector = `${itemContainerSelector}[data-${dataAttributeName}="${itemId}"]`;
+ const selectedItem = document.querySelector(selector);
+ if (selectedItem) {
+ selectedItem.classList.add('selected');
+ }
+}
+
+/**
+ * Generic linked items viewer
+ * Displays items that reference the current item
+ * @param {Array} linkedItems - Array of items to display
+ * @param {string} containerId - ID of container for the list
+ * @param {string} sectionId - ID of section wrapper (shown/hidden based on count)
+ * @param {Function} formatLinkedItem - Function that returns HTML for each linked item
+ * @param {Function} onLinkedItemClick - Callback when a linked item is clicked
+ */
+function updateGenericLinkedItems(linkedItems, containerId, sectionId, formatLinkedItem, onLinkedItemClick) {
+ const section = document.getElementById(sectionId);
+ const container = document.getElementById(containerId);
+
+ if (!section || !container) return;
+
+ if (linkedItems.length === 0) {
+ section.style.display = 'none';
+ return;
+ }
+
+ section.style.display = 'block';
+ const html = linkedItems.map(formatLinkedItem).join('');
+ container.innerHTML = html;
+
+ // Attach click handlers
+ container.querySelectorAll('[data-linked-item-id]').forEach(link => {
+ link.addEventListener('click', function(e) {
+ e.preventDefault();
+ const linkedItemId = this.dataset.linkedItemId;
+ onLinkedItemClick(linkedItemId);
+ });
+ });
+}
+
+/**
+ * Generic modal helper
+ * @param {string} modalId - ID of modal element
+ * @param {boolean} show - Whether to show (true) or hide (false)
+ * @param {Function} onShow - Optional callback when showing
+ * @param {Function} onHide - Optional callback when hiding
+ */
+function toggleGenericModal(modalId, show, onShow, onHide) {
+ const modal = document.getElementById(modalId);
+ if (!modal) return;
+
+ if (show) {
+ modal.style.display = 'flex';
+ if (onShow) onShow();
+ } else {
+ modal.style.display = 'none';
+ if (onHide) onHide();
+ }
+}
+
+/**
+ * Generic form show/hide helper
+ * @param {string} viewId - ID of view/form container
+ * @param {boolean} show - Whether to show (true) or hide (false)
+ * @param {Function} onShow - Optional callback when showing
+ * @param {Function} onHide - Optional callback when hiding
+ */
+function toggleGenericForm(viewId, show, onShow, onHide) {
+ const view = document.getElementById(viewId);
+ if (!view) return;
+
+ if (show) {
+ view.style.display = show === 'block' ? 'block' : 'flex';
+ if (onShow) onShow();
+ } else {
+ view.style.display = 'none';
+ if (onHide) onHide();
+ }
+}
+
+/**
+ * Generic element visibility toggler
+ * @param {string} elementId - ID of element to toggle
+ * @param {boolean} show - Whether to show or hide
+ */
+function setElementVisibility(elementId, show) {
+ const element = document.getElementById(elementId);
+ if (element) {
+ element.style.display = show ? 'block' : 'none';
+ }
+}
+
+/**
+ * Generic field populator for details panel
+ * @param {Object} data - Object with field data
+ * @param {Array} fieldMappings - Array of {fieldId, value} objects
+ */
+function populateDetailsPanel(data, fieldMappings) {
+ fieldMappings.forEach(({ fieldId, getValue }) => {
+ const element = document.getElementById(fieldId);
+ if (element) {
+ element.value = getValue ? getValue(data) : '';
+ }
+ });
+}
+
+/**
+ * Generic item deletion with confirmation
+ * @param {string} itemName - Name of item for confirmation message
+ * @param {string} warningMessage - Optional warning if item is in use
+ * @param {Function} onConfirm - Callback if user confirms deletion
+ * @returns {boolean} - Whether deletion was confirmed
+ */
+function confirmGenericDeletion(itemName, warningMessage = null, onConfirm) {
+ let message = `Delete "${itemName}"?`;
+
+ if (warningMessage) {
+ message = `⚠️ ${warningMessage}\n\nAre you sure you want to delete it?`;
+ } else {
+ message += ' This action cannot be undone.';
+ }
+
+ if (confirm(message)) {
+ if (onConfirm) onConfirm();
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Generic file download helper
+ * @param {string|Blob} content - Content to download
+ * @param {string} filename - Filename for download
+ * @param {string} mimeType - MIME type (default: 'text/plain')
+ */
+function downloadFile(content, filename, mimeType = 'text/plain') {
+ const blob = new Blob([content], { type: mimeType });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+}
+
+/**
+ * Clear file input helper
+ * @param {string} inputId - ID of file input element
+ */
+function clearFileInput(inputId) {
+ const input = document.getElementById(inputId);
+ if (input) {
+ input.value = '';
+ }
+}
+
+/**
+ * Generic text field update helper with debounce
+ * @param {string} fieldId - ID of input field
+ * @param {Function} onUpdate - Callback with new value
+ * @param {number} debounceMs - Debounce delay in milliseconds
+ */
+function setupDebouncedFieldUpdate(fieldId, onUpdate, debounceMs = 1000) {
+ const field = document.getElementById(fieldId);
+ if (!field) return;
+
+ let timeout;
+ field.addEventListener('input', function() {
+ clearTimeout(timeout);
+ timeout = setTimeout(() => {
+ onUpdate(this.value);
+ }, debounceMs);
+ });
+}
+
+/**
+ * Generic toggle button state updater
+ * @param {Array} buttons - Array of {id, isActive} objects
+ */
+function updateToggleButtons(buttons) {
+ buttons.forEach(({ id, isActive }) => {
+ const button = document.getElementById(id);
+ if (button) {
+ if (isActive) {
+ button.classList.add('active');
+ } else {
+ button.classList.remove('active');
+ }
+ }
+ });
+}
+
+/**
+ * Generic URL state updater with fallback
+ * Only updates if URLState global is available
+ * @param {Object} state - State object to pass to URLState.update
+ */
+function updateURLStateIfAvailable(state) {
+ if (typeof URLState !== 'undefined' && URLState.update) {
+ URLState.update(state);
+ }
+}
+
+/**
+ * Generic analytics tracker with fallback
+ * Only tracks if Analytics global is available
+ * @param {string} event - Event name
+ * @param {string} label - Event label
+ */
+function trackEventIfAvailable(event, label) {
+ if (typeof Analytics !== 'undefined' && Analytics.track) {
+ Analytics.track(event, label);
+ }
+}