Skill Name: ux-standards
Purpose: Enforce mandatory UX patterns for web SaaS development
Priority: CRITICAL - Auto-apply to all web UI development
When generating or modifying web UI code, AUTOMATICALLY:
Trigger: Any <select> for entities (stock items, customers, products, vendors, users, etc.)
Auto-generate:
// Initialize Select2 on all entity dropdowns
$('#entitySelect').select2({
placeholder: 'Search items...',
allowClear: true,
width: '100%',
dropdownParent: $('#modalId') // Auto-detect if in modal
});Detection patterns:
name="stock_item_id"→ Stock item selectname="product_id"→ Product selectname="customer_id"→ Customer selectname="unit_id"→ Unit select (UOM)idcontainsSelectorDropdown→ Likely needs Select2
Trigger: Any button that makes async calls
Auto-generate:
$('#submitBtn')
.html('<i class="spinner-border spinner-border-sm me-2"></i>Saving...')
.prop('disabled', true);
// After complete:
$('#submitBtn')
.html('<i class="bi bi-check me-2"></i>Save')
.prop('disabled', false);Trigger: Delete, cancel, or destructive action buttons
Auto-generate:
Swal.fire({
icon: 'warning',
title: 'Delete Item?',
text: 'This action cannot be undone',
showCancelButton: true,
confirmButtonText: 'Yes, Delete',
confirmButtonColor: '#d63939',
cancelButtonText: 'Cancel'
}).then((result) => {
if (result.isConfirmed) {
// Proceed with action
}
});Trigger: Any error handling code
Auto-generate:
try {
// API call
} catch (error) {
console.error('Technical error:', error); // Log technical details
Swal.fire({
icon: 'error',
title: 'Unable to Complete Action',
html: '<p>Please check your connection and try again.</p><p>If the problem persists, contact support.</p>'
});
}NEVER generate:
// BAD - Never show technical errors to users
alert(error.message);
Swal.fire('Error', error.toString(), 'error');Trigger: Lists/tables that might be empty
Auto-generate:
<div class="empty-state">
<div class="empty-state-icon">
<i class="bi bi-inbox"></i>
</div>
<h4>No Items Yet</h4>
<p class="text-muted">Get started by adding your first item</p>
<button class="btn btn-primary" onclick="openAddModal()">
<i class="bi bi-plus"></i> Add Item
</button>
</div>Trigger: Any list/table query fetching data
Auto-include in API:
$page = (int)($_GET['page'] ?? 1);
$limit = min((int)($_GET['limit'] ?? 50), 200);
$offset = ($page - 1) * $limit;
$sql .= " LIMIT ? OFFSET ?";
$params[] = $limit;
$params[] = $offset;Auto-add to all forms/tables:
@media (max-width: 768px) {
.data-table {
overflow-x: auto;
}
.modal {
margin: 0;
max-width: 100%;
}
}
### 8. Menu Design Rules (Mandatory)
**Trigger:** Any navigation menus for admin/member panels
**Auto-apply:**
- Keep menus minimal and easy on the eye.
- Group items by job role; a user should find their work in one menu.
- Each menu can have at most **5 submenus**.
- Each submenu can have at most **6 items**.
- If more items are needed, add **one** extra submenu level (no deeper).
- Use Bootstrap Icons on **all** menu headings and entries (`bi-*`).
- Prefer fewer pages by grouping related functions on a single page with permissioned sections/tabs/cards.Is it a dropdown for entities?
├─ YES → Add Select2
└─ NO → Continue
Does it load data async?
├─ YES → Add loading spinner
└─ NO → Continue
Is it a delete/destructive action?
├─ YES → Add confirmation dialog
└─ NO → Continue
Does it show errors?
├─ YES → Use user-friendly messages only
└─ NO → Continue
Can the list be empty?
├─ YES → Add empty state
└─ NO → Continue
Can the list have > 50 items?
├─ YES → Add pagination
└─ NO → Continue
Before marking code complete, verify:
- All entity dropdowns use Select2
- All async buttons show loading states
- All destructive actions have confirmations
- All errors are user-friendly
- All empty states have helpful content
- All lists > 50 items are paginated
- Responsive on mobile (320px width)
- Keyboard navigation works
<select id="stockItemSelect" name="stock_item_id" class="form-select" required style="width: 100%;">
<option value="">Select stock item...</option>
</select>
<small class="text-muted">Type to search</small>
<script>
$('#stockItemSelect').select2({
placeholder: 'Search stock items...',
allowClear: true,
width: '100%'
});
</script><select id="customerSelect" name="customer_id" class="form-select" style="width: 100%;">
<option value="">Select customer...</option>
</select>
<script>
$('#customerSelect').select2({
placeholder: 'Search customers...',
allowClear: true,
width: '100%',
ajax: {
url: './api/customers/search.php',
dataType: 'json',
delay: 250,
data: function (params) {
return { q: params.term, page: params.page || 1 };
},
processResults: function (data) {
return {
results: data.items.map(item => ({
id: item.id,
text: item.name
}))
};
}
},
minimumInputLength: 2
});
</script><button type="submit" id="submitBtn" class="btn btn-primary">
<i class="bi bi-check me-2"></i>Save
</button>
<script>
$('#form').on('submit', async function(e) {
e.preventDefault();
const $btn = $('#submitBtn');
$btn.html('<i class="spinner-border spinner-border-sm me-2"></i>Saving...').prop('disabled', true);
try {
const res = await fetch('./api/save.php', {
method: 'POST',
body: new FormData(this)
});
const data = await res.json();
if (data.success) {
Swal.fire('Success', data.message, 'success');
} else {
throw new Error(data.message);
}
} catch (error) {
console.error(error);
Swal.fire('Error', 'Unable to save. Please try again.', 'error');
} finally {
$btn.html('<i class="bi bi-check me-2"></i>Save').prop('disabled', false);
}
});
</script>function deleteItem(id, name) {
Swal.fire({
icon: 'warning',
title: 'Delete Item?',
html: `<p>This will permanently delete <strong>${name}</strong>.</p><p>This action cannot be undone.</p>`,
showCancelButton: true,
confirmButtonText: 'Yes, Delete',
confirmButtonColor: '#d63939',
cancelButtonText: 'Cancel',
focusCancel: true
}).then(async (result) => {
if (result.isConfirmed) {
try {
const res = await fetch(`./api/items/${id}`, { method: 'DELETE' });
const data = await res.json();
if (data.success) {
Swal.fire('Deleted', data.message, 'success');
reloadList();
} else {
throw new Error(data.message);
}
} catch (error) {
console.error(error);
Swal.fire('Error', 'Unable to delete. Please try again.', 'error');
}
}
});
}- Identify all dropdowns → Apply Select2
- Identify submit buttons → Add loading states
- Identify delete buttons → Add confirmations
- Wrap API calls in try-catch → User-friendly errors
- Add empty state HTML for lists
- Add pagination params to queries
- Search for
<select>→ Verify Select2 - Search for
delete→ Verify confirmation - Search for
fetch(→ Verify error handling - Search for
.innerHTML = ''→ Check for empty state - Search for SQL queries → Verify pagination
P0 - BLOCKING (must fix before deploy):
- Non-searchable dropdown with entity data
- Delete without confirmation
- Technical errors shown to users
- No loading state on async actions
P1 - HIGH (fix within sprint):
- Missing empty states
- Lists without pagination (> 50 items)
- Poor mobile responsiveness
P2 - MEDIUM (fix in next sprint):
- Missing keyboard shortcuts
- No success animations
- Inconsistent spacing/styling
- Full Standards:
/docs/standards/UX-STANDARDS.md - Quick Checklist:
/docs/standards/QUICK-UX-CHECKLIST.md
Last Updated: 2026-01-29 Status: ACTIVE - Enforce in all web development