---
name: ux-principles
description: User experience design principles for developers
domain: software-design
version: 1.0.0
tags: [ux, usability, accessibility, responsive, design-system, wcag]
triggers:
keywords:
primary: [ux, user experience, usability, accessibility, a11y, wcag]
secondary: [responsive, design system, heuristics, user flow, information architecture]
context_boost: [frontend, ui, design, user, interface]
context_penalty: [backend, database, devops]
priority: medium
---
# UX Principles
## Overview
Essential UX principles that every developer should know. Good UX isn't just design—it's built into code, architecture, and technical decisions.
---
## Nielsen's 10 Usability Heuristics
### 1. Visibility of System Status
```typescript
// ❌ No feedback
async function saveDocument() {
await api.save(document);
}
// ✅ Clear feedback
async function saveDocument() {
setStatus('saving');
try {
await api.save(document);
setStatus('saved');
showToast('Document saved');
} catch (error) {
setStatus('error');
showToast('Failed to save. Please try again.');
}
}
```
```html
{uploadProgress}% uploaded
```
### 2. Match Between System and Real World
```typescript
// ❌ Technical jargon
"Error: ECONNREFUSED 127.0.0.1:5432"
// ✅ Human language
"We couldn't connect to the database. Please check your internet connection."
// ❌ Developer terms
"Record not found in users table"
// ✅ User terms
"We couldn't find an account with that email address"
```
### 3. User Control and Freedom
```typescript
// Undo functionality
function deleteItem(id: string) {
const item = items.find(i => i.id === id);
setItems(items.filter(i => i.id !== id));
showToast({
message: 'Item deleted',
action: {
label: 'Undo',
onClick: () => setItems([...items, item])
},
duration: 5000
});
}
// Cancel long operations
const controller = new AbortController();
async function uploadFile(file: File) {
try {
await fetch('/upload', {
method: 'POST',
body: file,
signal: controller.signal
});
} catch (e) {
if (e.name === 'AbortError') {
showToast('Upload cancelled');
}
}
}
// User can cancel
```
### 4. Consistency and Standards
```typescript
// Design tokens for consistency
const theme = {
colors: {
primary: '#007bff',
danger: '#dc3545',
success: '#28a745',
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
},
borderRadius: {
sm: '4px',
md: '8px',
lg: '16px',
}
};
// Consistent button patterns
// Main action
// Secondary action
// Destructive action
```
### 5. Error Prevention
```typescript
// Confirm destructive actions
function deleteAccount() {
const confirmed = await confirm({
title: 'Delete Account?',
message: 'This action cannot be undone. All your data will be permanently deleted.',
confirmText: 'Delete Account',
confirmVariant: 'danger'
});
if (confirmed) {
await api.deleteAccount();
}
}
// Input constraints
// Disable invalid actions
```
---
## Accessibility (WCAG)
### Semantic HTML
```html
```
### ARIA Attributes
```html
{statusMessage}
Confirm Action
Are you sure you want to proceed?
Details here...
```
### Keyboard Navigation
```typescript
// Focus management
function openModal() {
setIsOpen(true);
// Focus first focusable element
setTimeout(() => {
modalRef.current?.querySelector('button, [href], input')?.focus();
}, 0);
}
function closeModal() {
setIsOpen(false);
// Return focus to trigger
triggerRef.current?.focus();
}
// Focus trap in modals
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Tab') {
const focusable = modalRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable?.[0];
const last = focusable?.[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last?.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first?.focus();
}
}
if (e.key === 'Escape') {
closeModal();
}
}
```
### Color and Contrast
```css
/* WCAG AA: 4.5:1 for normal text, 3:1 for large text */
:root {
--text-primary: #1a1a1a; /* High contrast on white */
--text-secondary: #6b7280; /* 4.5:1 on white */
--text-on-primary: #ffffff; /* White on brand color */
}
/* Don't rely on color alone */
.error-message {
color: #dc3545;
/* Also include icon */
&::before {
content: "⚠ ";
}
}
/* Focus indicators */
:focus-visible {
outline: 2px solid var(--focus-color);
outline-offset: 2px;
}
/* Never remove focus styles entirely */
/* ❌ */ :focus { outline: none; }
```
---
## Responsive Design
### Mobile-First Approach
```css
/* Base styles for mobile */
.container {
padding: 16px;
}
.grid {
display: grid;
gap: 16px;
grid-template-columns: 1fr;
}
/* Tablet and up */
@media (min-width: 768px) {
.container {
padding: 24px;
}
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop */
@media (min-width: 1024px) {
.container {
padding: 32px;
max-width: 1200px;
margin: 0 auto;
}
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
```
### Touch Targets
```css
/* Minimum 44x44px touch targets (WCAG) */
.button {
min-height: 44px;
min-width: 44px;
padding: 12px 16px;
}
/* Adequate spacing between interactive elements */
.button-group {
display: flex;
gap: 8px;
}
/* Make entire area tappable */
.card-link {
position: relative;
}
.card-link::after {
content: '';
position: absolute;
inset: 0;
}
```
---
## Performance as UX
### Perceived Performance
```typescript
// Optimistic updates
function likePost(postId: string) {
// Update UI immediately
setLiked(true);
setLikeCount(prev => prev + 1);
// Sync with server in background
api.likePost(postId).catch(() => {
// Rollback on failure
setLiked(false);
setLikeCount(prev => prev - 1);
showToast('Failed to like post');
});
}
// Skeleton loading
function PostList() {
if (isLoading) {
return (
{[1, 2, 3].map(i => (
))}
);
}
return {posts.map(renderPost)}
;
}
```
### Content Prioritization
```html
{hasMore && }
```
---
## Forms UX
### Input Design
```html
At least 8 characters
{hasError && (
Please enter a valid email address
)}
```
### Form Patterns
```typescript
// Auto-save drafts
const debouncedSave = useMemo(
() => debounce((data) => saveDraft(data), 1000),
[]
);
useEffect(() => {
debouncedSave(formData);
}, [formData]);
// Clear error on input
function handleChange(field: string, value: string) {
setFormData(prev => ({ ...prev, [field]: value }));
setErrors(prev => ({ ...prev, [field]: undefined }));
}
// Preserve form state on navigation
useBeforeUnload(
useCallback((e) => {
if (hasUnsavedChanges) {
e.preventDefault();
return 'You have unsaved changes';
}
}, [hasUnsavedChanges])
);
```
---
## Empty States
```tsx
function EmptyState({ type }: { type: 'search' | 'empty' | 'error' }) {
const content = {
search: {
icon: ,
title: 'No results found',
message: 'Try adjusting your search or filters',
action:
},
empty: {
icon: ,
title: 'No projects yet',
message: 'Create your first project to get started',
action:
},
error: {
icon: ,
title: 'Something went wrong',
message: 'We couldn\'t load the data. Please try again.',
action:
}
}[type];
return (
{content.icon}
{content.title}
{content.message}
{content.action}
);
}
```
---
## Related Skills
- [[frontend]] - UI implementation
- [[design-patterns]] - UI patterns
- [[accessibility]] - Detailed WCAG compliance