Initial commit — Singular Particular Space v1
Homepage (site/index.html): integration-v14 promoted, Writings section integrated with 33 pieces clustered by type (stories/essays/miscellany), Writings welcome lightbox, content frame at 98% opacity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
270
skills/commissioning-skill/SKILL.md
Normal file
270
skills/commissioning-skill/SKILL.md
Normal file
@@ -0,0 +1,270 @@
|
||||
---
|
||||
name: commissioning-skill
|
||||
description: >
|
||||
Use this skill whenever a parent (frontier) agent needs to commission a scion
|
||||
(small/fast) agent to work within a module. Triggers when assigning tasks to
|
||||
subagents, spawning scions, preparing module environments for agent execution,
|
||||
or when planning multi-agent task decomposition within a workspace that follows
|
||||
the Spore System architecture. Also triggers when the user says "commission a
|
||||
scion", "prepare a module for a subagent", "set up spores for", or "equip an
|
||||
agent to work on". Use this skill before spawning any scion — it is the
|
||||
mechanism that makes the scion's environment correct before execution begins.
|
||||
---
|
||||
|
||||
# Commissioning Skill
|
||||
|
||||
The commissioning skill equips a scion agent's environment before task execution.
|
||||
It crystallizes parent-agent reasoning into token-budgeted spore files so the
|
||||
scion executes against pre-resolved policy rather than re-deriving it at runtime.
|
||||
|
||||
**Core principle**: pay tokens once at commission time. The scion inherits
|
||||
decisions, not reasoning load.
|
||||
|
||||
---
|
||||
|
||||
## When to use this skill
|
||||
|
||||
Use this skill every time a parent agent is about to spawn a scion into a module.
|
||||
It runs *before* the scion starts — not during, not after. If a scion is already
|
||||
running without having been commissioned, stop it, commission properly, then restart.
|
||||
|
||||
---
|
||||
|
||||
## Inputs required
|
||||
|
||||
Before beginning, confirm you have:
|
||||
|
||||
- Task description (what the scion needs to accomplish)
|
||||
- Target module path (where the scion will operate)
|
||||
- Scion model class (determines token budget — see references/token-budgets.md)
|
||||
- Access to the module's `SEEDS.md` if it exists
|
||||
|
||||
If any of these are missing, ask before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## The commissioning sequence
|
||||
|
||||
Work through these steps in order. Do not skip steps.
|
||||
|
||||
### Step 1 — Read SEEDS.md
|
||||
|
||||
Navigate to the module root. Check for `SEEDS.md`.
|
||||
|
||||
**If SEEDS.md exists**: scan frontmatter entries only (not body). Look for an
|
||||
entry where `task-class` matches the current task and `status` is `active` or
|
||||
`provisional`. Do not read entry bodies yet — frontmatter scan only.
|
||||
|
||||
**If SEEDS.md does not exist**: proceed to Step 3 (compose path).
|
||||
|
||||
```
|
||||
Match found (active/provisional) → go to Step 2 (select path)
|
||||
No match / file absent → go to Step 3 (compose path)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 2 — Select path (precedent exists)
|
||||
|
||||
A seed entry exists for this task class. Use it.
|
||||
|
||||
Read the `spore-file` field from the matching seed entry frontmatter. Verify the
|
||||
referenced spore file exists at that path. If it does not exist (entry is stale),
|
||||
treat as no-match and proceed to Step 3.
|
||||
|
||||
Check the token count of the spore file against the budget for the scion model
|
||||
class (see references/token-budgets.md). If the file is within budget, proceed
|
||||
to Step 4. If it exceeds budget, prune before proceeding — see Pruning section.
|
||||
|
||||
If the seed entry status is `provisional`, note this. Flag to the parent after
|
||||
the scion completes its first run — if no escalation occurred on covered decisions,
|
||||
promote the entry to `active` in SEEDS.md.
|
||||
|
||||
---
|
||||
|
||||
### Step 3 — Compose path (no precedent)
|
||||
|
||||
No matching seed entry exists. Compose a new spore file from first principles.
|
||||
|
||||
**3a. Analyze the task and module tool surface**
|
||||
|
||||
Read the module SKILL.md if it exists. If no SKILL.md is present, proceed using
|
||||
the task description and module folder structure as your primary inputs — note
|
||||
in the seed entry body that no SKILL.md was found and the analysis was based on
|
||||
folder inspection alone. Identify which tool calls the scion is likely to make
|
||||
for this task class. Focus on:
|
||||
- File write/read operations (what paths, what constraints)
|
||||
- Bash command patterns (what is safe, what needs blocking)
|
||||
- Output format requirements (what the scion must produce)
|
||||
- Escalation triggers (what is genuinely novel vs. what is known)
|
||||
|
||||
**3b. Reason through the decision surface**
|
||||
|
||||
For each likely tool interaction, reason through: what should always be allowed,
|
||||
what should always be blocked, what requires contextual judgment (spore entry),
|
||||
and what is fully deterministic (hook candidate). Do this reasoning now, at
|
||||
commission time. The scion will not do this reasoning.
|
||||
|
||||
**3c. Write spore entries**
|
||||
|
||||
Write one entry per decision. Follow the entry format exactly — see
|
||||
references/spore-entry-format.md. Each entry must comply with:
|
||||
|
||||
- Maximum 60 tokens per entry (estimate; see token-budgets.md for model-specific guidance)
|
||||
- One trigger condition
|
||||
- One outcome (allow / deny / escalate)
|
||||
- No prose reasoning — reasoning belongs in the seed entry body, not here
|
||||
|
||||
**3d. Generate hooks where appropriate**
|
||||
|
||||
If the module does not yet have a `hooks/` directory, create it before writing
|
||||
any hook files. For any decision that is fully deterministic (no contextual
|
||||
judgment required), generate a hook file in `module/hooks/`. Use Python (stdlib
|
||||
only) for Python projects or any project without a stated language preference.
|
||||
Use JavaScript (Node.js, no packages) for TypeScript/JavaScript projects.
|
||||
|
||||
See references/hook-protocol.md for the raw protocol, self-contained templates
|
||||
in both languages, and validation instructions. No external SDK or library is
|
||||
required — the hook protocol is just stdin JSON in, exit code out.
|
||||
|
||||
Reference the hook in the spore entry's `hook:` field. Every hook must pass
|
||||
both a deny-trigger test and a safe-path test before registration. A spore
|
||||
that references a faulty hook is worse than no spore.
|
||||
|
||||
**3e. Write the spore file**
|
||||
|
||||
Write the composed entries to `module/spores/{task-class}-spores.md`. Use the
|
||||
task class as the filename stem. If a `default-spores.md` already exists and
|
||||
contains relevant entries, do not duplicate them — reference or inherit instead.
|
||||
|
||||
**3f. Write the seed entry**
|
||||
|
||||
Write a new entry to `SEEDS.md` at the module root. Status: `provisional`.
|
||||
Include:
|
||||
- Frontmatter: `task-class`, `spore-file`, `status`, `deposited`, `supersedes`
|
||||
- Body: the full reasoning you did in step 3b — why each policy was chosen,
|
||||
what edge cases were considered, what would trigger a revision. Write for a
|
||||
frontier model reading it later, not for the scion.
|
||||
|
||||
See references/seeds-entry-format.md for the full format.
|
||||
|
||||
---
|
||||
|
||||
### Step 4 — Enforce token budget
|
||||
|
||||
Count the tokens in the selected or composed spore file. Compare against the
|
||||
ceiling for the scion model class (references/token-budgets.md).
|
||||
|
||||
If within budget: proceed to Step 5.
|
||||
|
||||
If over budget: prune the spore file before proceeding.
|
||||
|
||||
**Pruning rules** (apply in this order until within budget):
|
||||
1. Remove entries marked `superseded` in SEEDS.md
|
||||
2. Remove entries whose hook has been deleted or rewritten
|
||||
3. Remove entries whose trigger condition no longer applies to the module's
|
||||
current tool surface
|
||||
4. If still over budget, flag to the parent — do not silently truncate entries
|
||||
that are still active. The parent must decide what to deprioritize.
|
||||
|
||||
---
|
||||
|
||||
### Step 5 — Spawn the scion
|
||||
|
||||
Spawn the scion with the following context, in this order (order matters for
|
||||
prompt caching — stable content first):
|
||||
|
||||
```
|
||||
1. Spore file contents ← stable; will cache across runs
|
||||
2. Module SKILL.md ← stable; will cache across runs
|
||||
3. Task brief ← variable; specific to this run
|
||||
4. Scoped tool permissions ← derived from spore entries and task
|
||||
```
|
||||
|
||||
Set the `SCION_SPORE_FILE` environment variable to the absolute path of the
|
||||
spore file before spawning. The session-start hook reads this variable to load
|
||||
spore contents into context on startup (see references/hook-patterns.md).
|
||||
|
||||
Declare the inheritance chain explicitly. If the scion operates in a component
|
||||
subfolder, state which spore files apply and in what order. Do not rely on
|
||||
implicit folder inheritance.
|
||||
|
||||
Do not give the scion access to SEEDS.md. SEEDS.md is parent-readable only.
|
||||
Do not give the scion access to the commissioning skill itself.
|
||||
|
||||
---
|
||||
|
||||
## Escalation handling
|
||||
|
||||
When the scion escalates a novel situation to the parent, handle it in two phases:
|
||||
|
||||
**Phase 1 — immediate resolution** (blocking; scion waits): reason through the
|
||||
novel situation and return a direct resolution to the scion so it can continue
|
||||
its task. Do not make the scion wait for spore deposition — unblocking the scion
|
||||
is the priority.
|
||||
|
||||
**Phase 2 — spore deposit** (after the scion is unblocked): add a new spore
|
||||
entry to the relevant spore file, add or update the seed entry in SEEDS.md with
|
||||
status `provisional`, and generate a hook if the decision is deterministic. This
|
||||
can happen while the scion continues executing.
|
||||
|
||||
The next scion to face the same situation encounters pre-resolved policy and
|
||||
never needs to escalate. The compound loop closes: each novel escalation becomes
|
||||
future policy at zero marginal cost per repeat encounter.
|
||||
|
||||
---
|
||||
|
||||
## Pruning vs. superseding
|
||||
|
||||
**Pruning** removes a spore entry from the file (reduces token count).
|
||||
Use when the trigger condition no longer applies.
|
||||
|
||||
**Superseding** marks an old seed entry as `superseded` and creates a new one.
|
||||
Use when the policy has changed but the trigger condition still applies.
|
||||
|
||||
Pruned entries disappear from the spore file. Superseded entries remain in
|
||||
SEEDS.md for audit but are skipped during routing. When you supersede, set
|
||||
`supersedes: path/to/old-spore-file` in the new seed entry's frontmatter.
|
||||
|
||||
---
|
||||
|
||||
## Component subfolder scions
|
||||
|
||||
A scion operating in a component subfolder (`module/component-x/`) should
|
||||
receive only the spore file relevant to its component, not the full module spore
|
||||
file. The commissioning skill selects or composes at the component level.
|
||||
|
||||
SEEDS.md is never placed in component subfolders. All seed entries for a module,
|
||||
including component-level decisions, live at the module root SEEDS.md. The
|
||||
frontmatter `task-class` field can use a namespaced convention to distinguish:
|
||||
`task-class: component-x/annotator`.
|
||||
|
||||
---
|
||||
|
||||
## Reference files
|
||||
|
||||
Read these when you need detail on a specific part of the workflow:
|
||||
|
||||
- `references/spore-entry-format.md` — full spore entry schema and examples
|
||||
- `references/seeds-entry-format.md` — full seed entry schema and examples
|
||||
- `references/token-budgets.md` — per-model token ceilings and rationale
|
||||
- `references/hook-protocol.md` — raw hook protocol, stdlib templates (Python + JS), validation
|
||||
|
||||
---
|
||||
|
||||
## What good commissioning looks like
|
||||
|
||||
A well-commissioned scion:
|
||||
- Receives a spore file that fits comfortably within its model class's token budget
|
||||
- Has hooks pre-installed for all fully deterministic decisions
|
||||
- Never needs to reason about whether to write to a `.env` file, what output
|
||||
format to use, or when to escalate — those decisions are already made
|
||||
- Escalates only genuinely novel situations, not recurrent known decisions
|
||||
- Completes its task faster and cheaper than an uncommissioned scion would
|
||||
|
||||
A poorly commissioned scion receives either too much context (bloated spore file,
|
||||
context rot risk) or too little (forced to re-derive known decisions, burning
|
||||
tokens on settled policy).
|
||||
|
||||
The goal is a scion that is maximally fast and focused because its environment
|
||||
has already done the thinking.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: frontend-design
|
||||
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
|
||||
|
||||
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
|
||||
|
||||
## Design Thinking
|
||||
|
||||
Before coding, understand the context and commit to a BOLD aesthetic direction:
|
||||
- **Purpose**: What problem does this interface solve? Who uses it?
|
||||
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
|
||||
- **Constraints**: Technical requirements (framework, performance, accessibility).
|
||||
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
|
||||
|
||||
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
|
||||
|
||||
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
|
||||
- Production-grade and functional
|
||||
- Visually striking and memorable
|
||||
- Cohesive with a clear aesthetic point-of-view
|
||||
- Meticulously refined in every detail
|
||||
|
||||
## Frontend Aesthetics Guidelines
|
||||
|
||||
Focus on:
|
||||
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
|
||||
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
|
||||
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
|
||||
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
|
||||
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
|
||||
|
||||
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
|
||||
|
||||
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
|
||||
|
||||
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
|
||||
|
||||
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
|
||||
42
skills/commissioning-skill/fetched-skills/ux-design/SKILL.md
Normal file
42
skills/commissioning-skill/fetched-skills/ux-design/SKILL.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: ux-design
|
||||
description: 'Composite UX/UI design skill combining refactoring-ui (visual hierarchy, spacing, color, depth) and ux-heuristics (usability, Nielsen heuristics, information architecture). Use for UI audits, visual improvements, and usability evaluation.'
|
||||
sources:
|
||||
- wondelai/skills/refactoring-ui v1.1.1
|
||||
- wondelai/skills/ux-heuristics v1.2.1
|
||||
fetched: 2026-03-26
|
||||
---
|
||||
|
||||
# UX Design — Composite Skill
|
||||
|
||||
This skill combines two complementary frameworks:
|
||||
|
||||
1. **refactoring-ui** — visual hierarchy, spacing, color, shadows, component styling.
|
||||
Full instructions: `refactoring-ui.md`
|
||||
References: `references/refui-*.md` (accessibility, advanced patterns, animation, data-viz, theming)
|
||||
|
||||
2. **ux-heuristics** — usability evaluation, Nielsen's 10 heuristics, Krug's laws, severity ratings.
|
||||
Full instructions: `ux-heuristics.md`
|
||||
References: `references/uxh-*.md` (audit template, cultural UX, dark patterns, heuristic conflicts, Krug, Nielsen, WCAG)
|
||||
|
||||
## When to load which
|
||||
|
||||
- **Building new UI**: Load `refactoring-ui.md` first, then `ux-heuristics.md` for review pass.
|
||||
- **Auditing existing UI**: Load `ux-heuristics.md` first (identify problems), then `refactoring-ui.md` (fix them).
|
||||
- **Accessibility review**: Load `references/uxh-wcag-checklist.md` + `references/refui-accessibility-depth.md`.
|
||||
- **Animation/motion work**: Load `references/refui-animation-microinteractions.md`.
|
||||
- **Dark theme work**: Load `references/refui-theming-dark-mode.md`.
|
||||
|
||||
## Quick reference for designers
|
||||
|
||||
### From refactoring-ui:
|
||||
- Design in grayscale first, add color last
|
||||
- Constrained scales for spacing, type, color, shadows
|
||||
- Not everything can be important — create hierarchy through size, weight, color
|
||||
- Start with too much white space, then remove
|
||||
|
||||
### From ux-heuristics:
|
||||
- "Don't Make Me Think" — every page should be self-evident
|
||||
- Users scan, they don't read. They satisfice, they don't optimize.
|
||||
- System status visibility, user control, consistency, error prevention
|
||||
- Match system to real world; recognition over recall
|
||||
@@ -0,0 +1,304 @@
|
||||
---
|
||||
name: refactoring-ui
|
||||
description: 'Audit and fix visual hierarchy, spacing, color, and depth in web UIs. Use when the user mentions "my UI looks off", "fix the design", "Tailwind styling", "color palette", or "visual hierarchy". Covers grayscale-first workflow, constrained design scales, shadows, and component styling. For typeface selection, see web-typography. For usability audits, see ux-heuristics.'
|
||||
license: MIT
|
||||
metadata:
|
||||
author: wondelai
|
||||
version: "1.1.1"
|
||||
---
|
||||
|
||||
# Refactoring UI Design System
|
||||
|
||||
A practical, opinionated approach to UI design. Apply these principles when generating frontend code, reviewing designs, or advising on visual improvements.
|
||||
|
||||
## Core Principle
|
||||
|
||||
**Design in grayscale first. Add color last.** This forces proper hierarchy through spacing, contrast, and typography before relying on color as a crutch.
|
||||
|
||||
**The foundation:** Great UI isn't about creativity or talent -- it's about systems. Constrained scales for spacing, type, color, and shadows produce consistently professional results. Start with too much white space, then remove. Details come later -- don't obsess over icons, shadows, or micro-interactions until the layout and hierarchy work.
|
||||
|
||||
## Scoring
|
||||
|
||||
**Goal: 10/10.** When reviewing or creating UI designs or frontend code, rate it 0-10 based on adherence to the principles below. A 10/10 means full alignment with all guidelines; lower scores indicate gaps to address. Always provide the current score and specific improvements needed to reach 10/10.
|
||||
|
||||
## The Refactoring UI Framework
|
||||
|
||||
Seven principles for building professional interfaces without a designer:
|
||||
|
||||
### 1. Visual Hierarchy
|
||||
|
||||
**Core concept:** Not everything can be important. Create hierarchy through three levers: size, weight, and color.
|
||||
|
||||
**Why it works:** When every element competes for attention, nothing stands out. Deliberate de-emphasis of secondary content makes primary content powerful by contrast.
|
||||
|
||||
**Key insights:**
|
||||
- Combine levers, don't multiply -- primary text = large OR bold OR dark, not all three
|
||||
- Save "all three" for the single most important element on the page
|
||||
- Labels are secondary -- form labels, table headers, and metadata labels support the data, not compete with it
|
||||
- Semantic color does not equal visual weight -- a muted red secondary button often works better than screaming danger for routine actions
|
||||
- De-emphasize labels by making them smaller, lighter, or uppercase-small
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Hierarchy Technique | Example |
|
||||
|---------|---------------------|---------|
|
||||
| **Form fields** | De-emphasize labels, emphasize values | Small uppercase label above large value text |
|
||||
| **Navigation** | Primary nav bold, secondary nav lighter | Active link in dark gray-900, inactive in gray-500 |
|
||||
| **Cards** | Title large, metadata small and light | Card title 20px bold, date 12px gray-400 |
|
||||
| **Dashboards** | Key metric large, context small | Revenue "$42,300" large, "vs last month" small |
|
||||
| **Tables** | De-emphasize headers, emphasize cell data | Headers uppercase small gray, data normal weight |
|
||||
|
||||
**Design patterns:**
|
||||
- Three-level hierarchy table: Size (large/base/small), Weight (bold/medium/normal), Color (dark/medium/light gray)
|
||||
- Label-value pattern: de-emphasized label above emphasized value
|
||||
- Button hierarchy: primary (filled), secondary (outlined or muted), tertiary (text only)
|
||||
|
||||
**Ethical boundary:** Don't use hierarchy tricks to hide important information like pricing, terms, or cancellation options.
|
||||
|
||||
See: [references/advanced-patterns.md](references/advanced-patterns.md) for interaction states and advanced component patterns.
|
||||
|
||||
### 2. Spacing & Sizing
|
||||
|
||||
**Core concept:** Use a constrained spacing scale, not arbitrary values. Spacing defines relationships -- elements closer together are more related.
|
||||
|
||||
**Why it works:** Arbitrary spacing (padding: 13px) creates inconsistency. A fixed scale forces deliberate decisions and produces harmonious layouts. Generous spacing feels premium; dense spacing feels overwhelming.
|
||||
|
||||
**Key insights:**
|
||||
- Use a linear or near-linear scale: 4, 8, 16, 24, 32, 48, 64px
|
||||
- Start with too much white space, then remove -- you'll almost never remove enough
|
||||
- Spacing between groups should be larger than spacing within groups
|
||||
- Text blocks should be constrained to 45-75 characters (`max-w-prose` or ~65ch)
|
||||
- Forms should max out at 300-500px width
|
||||
- Full-width is almost never right for content
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Spacing Strategy | Example |
|
||||
|---------|-----------------|---------|
|
||||
| **Icon + label** | Tight coupling (4px) | Small gap keeps them visually connected |
|
||||
| **Form fields** | Related elements (8-16px) | Input and its label tightly coupled |
|
||||
| **Card sections** | Section separation (24px) | Title block, content block, footer block |
|
||||
| **Page sections** | Major sections (48-64px) | Hero, features, testimonials, footer |
|
||||
| **Container width** | Constrain to content | `max-w-prose` for text, `max-w-md` for forms |
|
||||
|
||||
**CSS patterns:**
|
||||
- `p-1`(4px) `p-2`(8px) `p-4`(16px) `p-6`(24px) `p-8`(32px) `p-12`(48px) `p-16`(64px)
|
||||
- `max-w-prose`(65ch) `max-w-md`(28rem) `max-w-lg`(32rem) `max-w-xl`(36rem)
|
||||
- `gap-2` for related items, `gap-6` for section separation
|
||||
|
||||
**Ethical boundary:** Don't use spacing to bury important UI elements like unsubscribe buttons or privacy controls.
|
||||
|
||||
See: [references/advanced-patterns.md](references/advanced-patterns.md) for responsive breakpoint strategies.
|
||||
|
||||
### 3. Typography
|
||||
|
||||
**Core concept:** Use a modular type scale, constrain line heights by context, and limit to two font families maximum.
|
||||
|
||||
**Why it works:** A modular scale (e.g., 1.25 ratio) creates natural visual rhythm. Tight line heights on headings and relaxed line heights on body text improve readability across contexts.
|
||||
|
||||
**Key insights:**
|
||||
- Use a modular scale: 12, 14, 16, 20, 24, 30, 36px (1.25 ratio)
|
||||
- Headings need tight line height (1.0-1.25); body text needs relaxed (1.5-1.75)
|
||||
- Wider text needs more line height
|
||||
- Avoid font weights below 400 for body text -- they become unreadable
|
||||
- Use bold (600-700) for emphasis, not for everything
|
||||
- Two fonts maximum: one for headings, one for body (or one family with weight variation)
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Typography Rule | Example |
|
||||
|---------|----------------|---------|
|
||||
| **Hero headline** | 36px, tight line-height (1.1), bold | Large impactful statement |
|
||||
| **Section title** | 24px, line-height 1.25, semibold | Clear section demarcation |
|
||||
| **Body text** | 16px, line-height 1.75, normal weight | Comfortable reading |
|
||||
| **Captions/labels** | 12-14px, line-height 1.5, medium gray | Secondary information |
|
||||
| **Code/data** | Monospace, 14px, consistent width | Tabular data alignment |
|
||||
|
||||
**CSS patterns:**
|
||||
- `text-xs`(12px) `text-sm`(14px) `text-base`(16px) `text-lg`(18px) `text-xl`(20px)
|
||||
- `font-normal`(400) `font-medium`(500) `font-semibold`(600) `font-bold`(700)
|
||||
- `leading-tight`(1.25) `leading-normal`(1.5) `leading-relaxed`(1.75)
|
||||
|
||||
**Ethical boundary:** Don't use tiny type sizes to hide terms, conditions, or fees from users.
|
||||
|
||||
See: [references/advanced-patterns.md](references/advanced-patterns.md) for text truncation and responsive typography.
|
||||
|
||||
### 4. Color
|
||||
|
||||
**Core concept:** Build a systematic palette with 5-9 shades per color, add subtle saturation to grays, and design in grayscale first.
|
||||
|
||||
**Why it works:** Random colors clash. A systematic palette with predefined shades ensures consistency across the entire interface. HSL adjustments create natural-feeling lighter and darker variants.
|
||||
|
||||
**Key insights:**
|
||||
- Each color needs 5-9 shades from near-white to near-black (50 through 900)
|
||||
- The darkest shade is not black -- use 900-level dark grays (e.g., `#111827`) instead of pure `#000000`
|
||||
- Pure grays look lifeless -- add subtle saturation (cool UI: blue tint like `#64748b`; warm UI: yellow/brown tint like `#78716c`)
|
||||
- HSL adjustments: lighter = higher lightness, lower saturation, shift hue toward 60 degrees; darker = lower lightness, higher saturation, shift hue toward 0/240 degrees
|
||||
- Body text minimum 4.5:1 contrast ratio; large text (18px+) minimum 3:1
|
||||
- Use `#374151` (gray-700) on white, not lighter grays for readable text
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Color Strategy | Example |
|
||||
|---------|---------------|---------|
|
||||
| **Primary palette** | 9 shades (50-900) for main brand color | Blue-500 for buttons, Blue-100 for backgrounds |
|
||||
| **Gray palette** | Saturated grays matching UI temperature | Cool grays with blue tint for tech products |
|
||||
| **Semantic colors** | Success, warning, error each with shade range | Green-500 for success, Red-500 for errors |
|
||||
| **Text colors** | Three levels: dark, medium, light | `text-gray-900`, `text-gray-600`, `text-gray-400` |
|
||||
| **Accessible contrast** | Test all text/background combos | `#374151` on white = 10.5:1 ratio |
|
||||
|
||||
**CSS patterns:**
|
||||
- `text-gray-900`(dark) `text-gray-600`(medium) `text-gray-400`(light)
|
||||
- `bg-blue-50` for subtle backgrounds, `bg-blue-500` for primary actions
|
||||
- `border-gray-200` for subtle borders, `border-gray-300` for stronger
|
||||
|
||||
**Ethical boundary:** Don't use color alone to convey information -- always pair with text or icons for accessibility.
|
||||
|
||||
See: [references/theming-dark-mode.md](references/theming-dark-mode.md) for dark palette creation and theme implementation.
|
||||
|
||||
### 5. Depth & Shadows
|
||||
|
||||
**Core concept:** Use a shadow scale to convey elevation. Small shadows for slightly raised elements, large shadows for floating elements.
|
||||
|
||||
**Why it works:** Shadows create a sense of physical depth that helps users understand which elements are interactive, which are floating above the surface, and which are part of the background.
|
||||
|
||||
**Key insights:**
|
||||
- Small shadows = raised slightly (buttons, cards); large shadows = floating (modals, dropdowns)
|
||||
- Shadows have two parts: a tight, dark shadow for crispness plus a larger, softer shadow for atmosphere
|
||||
- Depth without shadows: lighter top border + darker bottom border, subtle gradient backgrounds, overlapping elements with offset
|
||||
- Don't overuse shadows -- if everything floats, nothing has depth
|
||||
- Shadow color should be transparent dark, not opaque gray
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Shadow Level | Example |
|
||||
|---------|-------------|---------|
|
||||
| **Buttons** | `shadow-sm` (subtle raise) | Slightly elevated above page surface |
|
||||
| **Cards** | `shadow-md` (clear separation) | Content grouped and lifted from background |
|
||||
| **Dropdowns** | `shadow-lg` (floating) | Menu clearly floating above content |
|
||||
| **Modals** | `shadow-xl` (highest elevation) | Overlay clearly detached from page |
|
||||
| **Flat alternatives** | Border + background shift | Lighter top border, darker bottom border |
|
||||
|
||||
**CSS patterns:**
|
||||
- `shadow-sm`: `0 1px 2px rgba(0,0,0,0.05)`
|
||||
- `shadow-md`: `0 4px 6px rgba(0,0,0,0.1)`
|
||||
- `shadow-lg`: `0 10px 15px rgba(0,0,0,0.1)`
|
||||
- `shadow-xl`: `0 20px 25px rgba(0,0,0,0.15)`
|
||||
|
||||
**Ethical boundary:** Don't use excessive shadows or visual emphasis to draw attention to deceptive UI elements (dark patterns).
|
||||
|
||||
See: [references/advanced-patterns.md](references/advanced-patterns.md) for interaction states and elevation hierarchy.
|
||||
|
||||
### 6. Images & Icons
|
||||
|
||||
**Core concept:** Treat images as design elements, not afterthoughts. Size icons deliberately and use overlays to ensure text readability on images.
|
||||
|
||||
**Why it works:** Poorly sized icons look awkward. Unstyled images break visual consistency. Deliberate image treatment (overlays, object-fit, border radius) makes interfaces feel polished.
|
||||
|
||||
**Key insights:**
|
||||
- Icons should be sized relative to their context -- don't use the same size everywhere
|
||||
- Use icon sets with consistent stroke width and style
|
||||
- Images need treatment: object-fit cover, consistent aspect ratios, overlays for text
|
||||
- Don't stretch or distort images -- use `object-fit: cover` and crop deliberately
|
||||
- Empty states are an opportunity -- use illustrations, not just text
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Image/Icon Technique | Example |
|
||||
|---------|---------------------|---------|
|
||||
| **Hero images** | Overlay with semi-transparent gradient | Text readable over any photo |
|
||||
| **Avatars** | Consistent size, rounded, fallback initials | 40px circle with object-fit cover |
|
||||
| **Feature icons** | Consistent size, weight, and color | 24px stroke icons in gray-500 |
|
||||
| **Empty states** | Custom illustration + clear CTA | Friendly illustration with "Get started" button |
|
||||
| **Thumbnails** | Fixed aspect ratio with object-fit cover | 16:9 cards with no distortion |
|
||||
|
||||
**CSS patterns:**
|
||||
- `object-fit: cover` with fixed `aspect-ratio` for consistent image display
|
||||
- Icon sizing: `w-4 h-4` inline, `w-6 h-6` in navigation, `w-8 h-8` for feature icons
|
||||
- Image overlay: `bg-gradient-to-t from-black/60 to-transparent` for text on images
|
||||
|
||||
**Ethical boundary:** Don't use misleading images or icons that misrepresent functionality or product capabilities.
|
||||
|
||||
See: [references/advanced-patterns.md](references/advanced-patterns.md) for image treatment, icon usage, and empty states.
|
||||
|
||||
### 7. Layout & Composition
|
||||
|
||||
**Core concept:** Don't center everything. Use alignment, overlap, and emphasis variation to create engaging compositions.
|
||||
|
||||
**Why it works:** Left-aligned text is easier to read. Varied layouts keep users engaged. Breaking out of rigid boxes makes designs feel dynamic and intentional.
|
||||
|
||||
**Key insights:**
|
||||
- Left-align text by default; center only short headlines, hero sections, single-action CTAs, and empty states
|
||||
- Cards don't need to contain everything -- let images bleed to edges, overlap containers, or extend beyond bounds
|
||||
- In lists and feeds, vary the visual treatment -- feature some items, minimize others
|
||||
- Use alignment to create visual relationships between unrelated elements
|
||||
- Alternate emphasis: not every card in a list needs the same layout
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Layout Strategy | Example |
|
||||
|---------|----------------|---------|
|
||||
| **Hero sections** | Centered text, generous spacing | Short headline + subtext + single CTA |
|
||||
| **Feature grids** | Left-aligned text, consistent card sizes | 3-column grid with icon + title + description |
|
||||
| **Blog feeds** | Varied card sizes for emphasis | First post large, next posts in 2-column grid |
|
||||
| **Sidebars** | Narrower than main content, lighter background | Navigation or filters at 240-320px width |
|
||||
| **Content pages** | Constrained width, left-aligned | `max-w-prose` centered container with left text |
|
||||
|
||||
**CSS patterns:**
|
||||
- `text-left` by default, `text-center` only for heroes and short headlines
|
||||
- `grid grid-cols-3 gap-6` for feature grids
|
||||
- `max-w-4xl mx-auto` for page containers
|
||||
- `overflow-hidden` on cards with `object-fit: cover` images that bleed to edges
|
||||
|
||||
**Ethical boundary:** Don't use layout tricks to hide or obscure important user choices like opt-outs or data permissions.
|
||||
|
||||
See: [references/advanced-patterns.md](references/advanced-patterns.md) for responsive breakpoints and complex layout patterns.
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
| Mistake | Why It Fails | Fix |
|
||||
|---------|-------------|------|
|
||||
| **"Looks amateur"** | Insufficient white space, unconstrained widths | Add more white space, constrain content widths |
|
||||
| **"Feels flat"** | No depth differentiation between elements | Add subtle shadows, border-bottom on sections |
|
||||
| **"Text is hard to read"** | Poor line-height, too wide, low contrast | Increase line-height, constrain width, boost contrast |
|
||||
| **"Everything looks the same"** | No visual hierarchy between elements | Vary size/weight/color between primary and secondary |
|
||||
| **"Feels cluttered"** | Equal spacing everywhere, no grouping | Group related items, increase spacing between groups |
|
||||
| **"Colors clash"** | Random color choices without a system | Reduce saturation, use more grays, limit palette to system |
|
||||
| **"Buttons don't pop"** | Low contrast with surrounding elements | Increase contrast with surroundings, add shadow |
|
||||
| **Using arbitrary values** | px values like 13, 17, 23 create inconsistency | Stick to the spacing and type scales |
|
||||
|
||||
## Quick Diagnostic
|
||||
|
||||
Audit any UI design:
|
||||
|
||||
| Question | If No | Action |
|
||||
|----------|-------|--------|
|
||||
| Does hierarchy read when squinting (blur test)? | Elements competing for attention | Increase contrast between primary and secondary |
|
||||
| Does it work in grayscale? | Relying on color for hierarchy | Strengthen size/weight/spacing hierarchy |
|
||||
| Is there enough white space? | Probably not -- most designs are too dense | Increase spacing, especially between groups |
|
||||
| Are labels de-emphasized vs. their values? | Labels competing with data | Make labels smaller, lighter, or uppercase-small |
|
||||
| Does spacing follow a consistent scale? | Arbitrary spacing creates visual noise | Use 4/8/16/24/32/48/64 scale only |
|
||||
| Is text width constrained for readability? | Long lines cause reader fatigue | Apply `max-w-prose` (~65ch) to text blocks |
|
||||
| Do colors have sufficient contrast? | Accessibility failure, hard to read | Test with WCAG contrast checker, use gray-700+ on white |
|
||||
| Are shadows appropriate for elevation? | Elements floating at wrong visual level | Match shadow scale to element purpose |
|
||||
|
||||
## Reference Files
|
||||
|
||||
- [advanced-patterns.md](references/advanced-patterns.md): Empty states, form design, image treatment, icon sizing, interaction states, color psychology, border radius systems, text truncation, responsive breakpoints
|
||||
- [animation-microinteractions.md](references/animation-microinteractions.md): When to animate, easing functions, durations, loading states, animation performance
|
||||
- [accessibility-depth.md](references/accessibility-depth.md): WCAG 2.1 AA checklist, focus management, screen reader support, keyboard navigation
|
||||
- [data-visualization.md](references/data-visualization.md): Chart selection, color in charts, table design, dashboard layouts
|
||||
- [theming-dark-mode.md](references/theming-dark-mode.md): Dark palette creation, elevation in dark mode, theme implementation strategies
|
||||
|
||||
## Further Reading
|
||||
|
||||
This skill is based on Adam Wathan and Steve Schoger's practical design guide. For the complete system with visual examples:
|
||||
|
||||
- [*"Refactoring UI"*](https://www.amazon.com/Refactoring-UI-Adam-Wathan/dp/B0BLJ7MC21?tag=wondelai00-20) by Adam Wathan & Steve Schoger (the full book with hundreds of visual before/after examples)
|
||||
- [*"The Design of Everyday Things"*](https://www.amazon.com/Design-Everyday-Things-Revised-Expanded/dp/0465050654?tag=wondelai00-20) by Don Norman (foundational design thinking and usability)
|
||||
- [*"Don't Make Me Think"*](https://www.amazon.com/Dont-Make-Think-Revisited-Usability/dp/0321965515?tag=wondelai00-20) by Steve Krug (web usability principles that complement Refactoring UI)
|
||||
- [Refactoring UI](https://www.refactoringui.com/) -- Official site with resources and examples
|
||||
|
||||
## About the Authors
|
||||
|
||||
**Adam Wathan** is a full-stack developer and the creator of Tailwind CSS, one of the most popular utility-first CSS frameworks. **Steve Schoger** is a visual designer known for his practical design tips and illustrations. Together they created *Refactoring UI* to teach developers how to design better interfaces using systematic, repeatable techniques rather than relying on innate artistic talent. Their approach emphasizes constrained design systems -- fixed scales for spacing, typography, color, and shadows -- that produce professional results without requiring a design background.
|
||||
@@ -0,0 +1,507 @@
|
||||
# Accessibility Deep Dive
|
||||
|
||||
Comprehensive WCAG 2.1 AA compliance checklist with practical implementation guidance.
|
||||
|
||||
## Accessibility Philosophy
|
||||
|
||||
Accessibility isn't just compliance—it improves UX for everyone:
|
||||
- Keyboard navigation helps power users
|
||||
- Good contrast helps in bright sunlight
|
||||
- Clear focus states help everyone understand what's selected
|
||||
- Proper headings help screen readers AND SEO
|
||||
|
||||
---
|
||||
|
||||
## WCAG 2.1 AA Checklist
|
||||
|
||||
### Perceivable
|
||||
|
||||
Users must be able to perceive content.
|
||||
|
||||
#### 1.1 Text Alternatives
|
||||
|
||||
**All images need alt text:**
|
||||
|
||||
```html
|
||||
<!-- Decorative image (no alt) -->
|
||||
<img src="divider.svg" alt="" role="presentation">
|
||||
|
||||
<!-- Informative image -->
|
||||
<img src="chart.png" alt="Sales increased 40% from Q1 to Q2">
|
||||
|
||||
<!-- Functional image (button/link) -->
|
||||
<button>
|
||||
<img src="search.svg" alt="Search">
|
||||
</button>
|
||||
|
||||
<!-- Complex image (needs long description) -->
|
||||
<figure>
|
||||
<img src="diagram.png" alt="System architecture overview" aria-describedby="diagram-desc">
|
||||
<figcaption id="diagram-desc">Detailed description of the system architecture...</figcaption>
|
||||
</figure>
|
||||
```
|
||||
|
||||
**Icons:**
|
||||
```html
|
||||
<!-- Icon with visible text (icon is decorative) -->
|
||||
<button>
|
||||
<svg aria-hidden="true">...</svg>
|
||||
<span>Settings</span>
|
||||
</button>
|
||||
|
||||
<!-- Icon-only button -->
|
||||
<button aria-label="Settings">
|
||||
<svg aria-hidden="true">...</svg>
|
||||
</button>
|
||||
```
|
||||
|
||||
#### 1.3 Adaptable
|
||||
|
||||
**Semantic HTML structure:**
|
||||
|
||||
```html
|
||||
<!-- Use semantic elements -->
|
||||
<header>...</header>
|
||||
<nav>...</nav>
|
||||
<main>
|
||||
<article>
|
||||
<h1>Page Title</h1>
|
||||
<section>
|
||||
<h2>Section Title</h2>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
<footer>...</footer>
|
||||
|
||||
<!-- Not this -->
|
||||
<div class="header">...</div>
|
||||
<div class="nav">...</div>
|
||||
```
|
||||
|
||||
**Heading hierarchy:**
|
||||
- One `<h1>` per page
|
||||
- Don't skip levels (h1 → h3)
|
||||
- Headings describe content structure
|
||||
|
||||
**Form labels:**
|
||||
```html
|
||||
<!-- Explicit label -->
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="email">
|
||||
|
||||
<!-- Implicit label -->
|
||||
<label>
|
||||
Email
|
||||
<input type="email">
|
||||
</label>
|
||||
|
||||
<!-- Hidden label (for visual designs without labels) -->
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<input id="search" type="search" placeholder="Search...">
|
||||
```
|
||||
|
||||
#### 1.4 Distinguishable
|
||||
|
||||
**Color contrast requirements:**
|
||||
|
||||
| Content Type | Minimum Ratio | Tool |
|
||||
|--------------|---------------|------|
|
||||
| Normal text (<18px) | 4.5:1 | WebAIM Contrast Checker |
|
||||
| Large text (≥18px or ≥14px bold) | 3:1 | |
|
||||
| UI components & graphics | 3:1 | |
|
||||
|
||||
**Common contrast fixes:**
|
||||
|
||||
```css
|
||||
/* Too light - fails */
|
||||
.text-light { color: #9ca3af; } /* gray-400: 3.1:1 on white */
|
||||
|
||||
/* Passes AA */
|
||||
.text-muted { color: #6b7280; } /* gray-500: 4.6:1 on white */
|
||||
|
||||
/* Passes AAA */
|
||||
.text-strong { color: #374151; } /* gray-700: 9.1:1 on white */
|
||||
```
|
||||
|
||||
**Don't rely on color alone:**
|
||||
|
||||
```html
|
||||
<!-- Bad: only color indicates error -->
|
||||
<input class="border-red-500">
|
||||
|
||||
<!-- Good: color + icon + text -->
|
||||
<input class="border-red-500" aria-invalid="true" aria-describedby="error">
|
||||
<p id="error" class="text-red-600">
|
||||
<svg aria-hidden="true">⚠️</svg>
|
||||
Email is required
|
||||
</p>
|
||||
```
|
||||
|
||||
**Text resize:**
|
||||
- Content must be readable at 200% zoom
|
||||
- Use relative units (rem, em) not px for text
|
||||
- Test by zooming browser to 200%
|
||||
|
||||
---
|
||||
|
||||
### Operable
|
||||
|
||||
Users must be able to operate the interface.
|
||||
|
||||
#### 2.1 Keyboard Accessible
|
||||
|
||||
**All functionality must work with keyboard:**
|
||||
|
||||
| Key | Expected Behavior |
|
||||
|-----|-------------------|
|
||||
| Tab | Move to next focusable element |
|
||||
| Shift+Tab | Move to previous focusable element |
|
||||
| Enter | Activate links, buttons |
|
||||
| Space | Activate buttons, toggle checkboxes |
|
||||
| Arrows | Navigate within components (tabs, menus, radios) |
|
||||
| Escape | Close modals, dropdowns, cancel actions |
|
||||
|
||||
**Focus must be visible:**
|
||||
|
||||
```css
|
||||
/* Don't remove focus outlines */
|
||||
:focus {
|
||||
outline: none; /* ❌ Never do this without replacement */
|
||||
}
|
||||
|
||||
/* Do provide visible focus */
|
||||
:focus-visible {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Or use ring utility */
|
||||
.focusable:focus-visible {
|
||||
@apply ring-2 ring-blue-500 ring-offset-2;
|
||||
}
|
||||
```
|
||||
|
||||
**Keyboard traps:**
|
||||
- Modal dialogs should trap focus inside
|
||||
- But must have a way to exit (Escape key, close button)
|
||||
|
||||
```jsx
|
||||
// Focus trap for modals
|
||||
function Modal({ isOpen, onClose, children }) {
|
||||
const modalRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
// Focus first focusable element
|
||||
const firstFocusable = modalRef.current.querySelector('button, input, a');
|
||||
firstFocusable?.focus();
|
||||
|
||||
// Trap focus inside
|
||||
const handleTab = (e) => {
|
||||
if (e.key === 'Tab') {
|
||||
// ... trap logic
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleTab);
|
||||
return () => document.removeEventListener('keydown', handleTab);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// Return focus on close
|
||||
}
|
||||
```
|
||||
|
||||
**Skip links:**
|
||||
|
||||
```html
|
||||
<body>
|
||||
<a href="#main-content" class="sr-only focus:not-sr-only">
|
||||
Skip to main content
|
||||
</a>
|
||||
<nav>...</nav>
|
||||
<main id="main-content">...</main>
|
||||
</body>
|
||||
```
|
||||
|
||||
#### 2.4 Navigable
|
||||
|
||||
**Page titles:**
|
||||
- Unique, descriptive page titles
|
||||
- Format: `Page Name | Site Name`
|
||||
|
||||
**Focus order:**
|
||||
- Must follow logical reading order
|
||||
- Don't use positive `tabindex` values (messes up order)
|
||||
- Only use `tabindex="0"` (make focusable) or `tabindex="-1"` (programmatically focusable)
|
||||
|
||||
**Link purpose:**
|
||||
```html
|
||||
<!-- Bad -->
|
||||
<a href="/article">Click here</a>
|
||||
<a href="/article">Read more</a>
|
||||
|
||||
<!-- Good -->
|
||||
<a href="/article">Read more about accessibility best practices</a>
|
||||
|
||||
<!-- Or with context -->
|
||||
<a href="/article" aria-describedby="article-title">Read more</a>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Understandable
|
||||
|
||||
Users must be able to understand content and operation.
|
||||
|
||||
#### 3.1 Readable
|
||||
|
||||
**Language declaration:**
|
||||
```html
|
||||
<html lang="en">
|
||||
<body>
|
||||
<p>This is English.</p>
|
||||
<p lang="fr">Ceci est français.</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 3.2 Predictable
|
||||
|
||||
**Consistent navigation:**
|
||||
- Same navigation in same location across pages
|
||||
- Same elements behave the same way
|
||||
|
||||
**No unexpected changes:**
|
||||
- Form inputs don't auto-submit on change
|
||||
- No unexpected pop-ups
|
||||
- Focus doesn't move unexpectedly
|
||||
|
||||
```html
|
||||
<!-- Bad: changes page on select -->
|
||||
<select onchange="window.location = this.value">...</select>
|
||||
|
||||
<!-- Good: requires explicit action -->
|
||||
<select id="region">...</select>
|
||||
<button onclick="navigate()">Go</button>
|
||||
```
|
||||
|
||||
#### 3.3 Input Assistance
|
||||
|
||||
**Error identification:**
|
||||
```html
|
||||
<label for="email">Email</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
aria-invalid="true"
|
||||
aria-describedby="email-error"
|
||||
>
|
||||
<p id="email-error" class="error">
|
||||
Please enter a valid email address (e.g., name@example.com)
|
||||
</p>
|
||||
```
|
||||
|
||||
**Required fields:**
|
||||
```html
|
||||
<label for="name">
|
||||
Name <span aria-hidden="true">*</span>
|
||||
<span class="sr-only">(required)</span>
|
||||
</label>
|
||||
<input id="name" required aria-required="true">
|
||||
```
|
||||
|
||||
**Error prevention for critical actions:**
|
||||
- Confirm destructive actions
|
||||
- Allow review before submission
|
||||
- Provide undo capability
|
||||
|
||||
---
|
||||
|
||||
### Robust
|
||||
|
||||
Content must work with current and future technologies.
|
||||
|
||||
#### 4.1 Compatible
|
||||
|
||||
**Valid HTML:**
|
||||
- Unique IDs
|
||||
- Complete start/end tags
|
||||
- Proper nesting
|
||||
|
||||
**ARIA usage:**
|
||||
```html
|
||||
<!-- If you use ARIA, use it correctly -->
|
||||
|
||||
<!-- Roles -->
|
||||
<div role="button" tabindex="0" onclick="...">Fake Button</div>
|
||||
<!-- Better: just use <button> -->
|
||||
|
||||
<!-- States -->
|
||||
<button aria-pressed="true">Bold</button>
|
||||
<button aria-expanded="false" aria-controls="menu">Menu</button>
|
||||
|
||||
<!-- Live regions -->
|
||||
<div aria-live="polite" aria-atomic="true">
|
||||
<!-- Screen reader announces changes here -->
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Focus Management
|
||||
|
||||
### When to Manage Focus
|
||||
|
||||
| Scenario | Focus Action |
|
||||
|----------|--------------|
|
||||
| Modal opens | Focus first element inside modal |
|
||||
| Modal closes | Return focus to trigger element |
|
||||
| Error occurs | Focus error message or first invalid field |
|
||||
| New content loads | Focus heading or first new element |
|
||||
| Item deleted | Focus previous/next item or container |
|
||||
|
||||
### Implementation
|
||||
|
||||
```jsx
|
||||
// Store trigger reference
|
||||
const triggerRef = useRef();
|
||||
|
||||
function openModal() {
|
||||
triggerRef.current = document.activeElement;
|
||||
setIsOpen(true);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
setIsOpen(false);
|
||||
// Return focus after state update
|
||||
setTimeout(() => triggerRef.current?.focus(), 0);
|
||||
}
|
||||
```
|
||||
|
||||
### Roving Tabindex (for component groups)
|
||||
|
||||
```jsx
|
||||
// Tab panels, menu items, radio groups
|
||||
function Tabs({ tabs }) {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'ArrowRight') {
|
||||
setActiveIndex((activeIndex + 1) % tabs.length);
|
||||
}
|
||||
if (e.key === 'ArrowLeft') {
|
||||
setActiveIndex((activeIndex - 1 + tabs.length) % tabs.length);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div role="tablist" onKeyDown={handleKeyDown}>
|
||||
{tabs.map((tab, i) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
role="tab"
|
||||
tabIndex={i === activeIndex ? 0 : -1}
|
||||
aria-selected={i === activeIndex}
|
||||
ref={i === activeIndex ? (el) => el?.focus() : null}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Screen Reader Considerations
|
||||
|
||||
### Announce Dynamic Changes
|
||||
|
||||
```html
|
||||
<!-- Live region for status messages -->
|
||||
<div aria-live="polite" class="sr-only" id="status"></div>
|
||||
|
||||
<script>
|
||||
function showSuccess(message) {
|
||||
document.getElementById('status').textContent = message;
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Hide Decorative Content
|
||||
|
||||
```html
|
||||
<!-- Hidden from screen readers -->
|
||||
<svg aria-hidden="true">...</svg>
|
||||
<span aria-hidden="true">•</span>
|
||||
```
|
||||
|
||||
### Provide Context
|
||||
|
||||
```html
|
||||
<!-- Ambiguous button -->
|
||||
<button>Delete</button>
|
||||
|
||||
<!-- Clear button -->
|
||||
<button aria-label="Delete comment by John">Delete</button>
|
||||
|
||||
<!-- Or use aria-describedby -->
|
||||
<button aria-describedby="comment-123-author">Delete</button>
|
||||
<span id="comment-123-author" class="sr-only">comment by John</span>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Tools
|
||||
|
||||
### Automated Testing
|
||||
|
||||
| Tool | What It Catches |
|
||||
|------|-----------------|
|
||||
| axe DevTools | ~30% of WCAG issues |
|
||||
| WAVE | Similar to axe, visual overlay |
|
||||
| Lighthouse | Basic accessibility audit |
|
||||
| ESLint a11y plugin | Catches issues in JSX |
|
||||
|
||||
### Manual Testing Required
|
||||
|
||||
Automated tools miss ~70% of issues. Manual testing needed for:
|
||||
- Keyboard navigation flow
|
||||
- Screen reader experience
|
||||
- Focus management
|
||||
- Meaningful alt text
|
||||
- Logical heading structure
|
||||
|
||||
### Testing Checklist
|
||||
|
||||
- [ ] Navigate entire page with keyboard only
|
||||
- [ ] Test with screen reader (VoiceOver, NVDA)
|
||||
- [ ] Check color contrast with tool
|
||||
- [ ] Zoom to 200% and verify usability
|
||||
- [ ] Test with high contrast mode
|
||||
- [ ] Verify focus indicators visible
|
||||
- [ ] Check heading structure with outline tool
|
||||
- [ ] Run axe DevTools audit
|
||||
- [ ] Test forms with validation errors
|
||||
|
||||
---
|
||||
|
||||
## Common Fixes Quick Reference
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| Missing alt text | Add descriptive alt or `alt=""` for decorative |
|
||||
| Low contrast | Use gray-600+ for text on white |
|
||||
| Missing focus style | Add `focus-visible` ring/outline |
|
||||
| Click-only interaction | Add keyboard handler + focusability |
|
||||
| Missing form labels | Add `<label>` with `for` attribute |
|
||||
| Heading skip | Use h1→h2→h3 in order |
|
||||
| Color-only indicator | Add icon/text alongside color |
|
||||
| Modal focus trap | Trap focus, allow Escape to close |
|
||||
| Auto-playing media | Add pause control, don't autoplay |
|
||||
| Motion | Respect `prefers-reduced-motion` |
|
||||
@@ -0,0 +1,472 @@
|
||||
# Advanced Design Patterns
|
||||
|
||||
Extended reference for complex scenarios. Load only when deeper guidance needed.
|
||||
|
||||
## Table of Contents
|
||||
1. [Empty States](#empty-states)
|
||||
2. [Form Design](#form-design)
|
||||
3. [Image Treatment](#image-treatment)
|
||||
4. [Icon Usage](#icon-usage)
|
||||
5. [Interaction States](#interaction-states)
|
||||
6. [Color Psychology](#color-psychology)
|
||||
7. [Border Radius System](#border-radius-system)
|
||||
|
||||
---
|
||||
|
||||
## Empty States
|
||||
|
||||
Empty states are opportunities, not afterthoughts.
|
||||
|
||||
**Good empty states include:**
|
||||
- Illustration or icon (not generic)
|
||||
- Clear explanation of what goes here
|
||||
- Primary action to remedy emptiness
|
||||
- Optional secondary actions
|
||||
|
||||
```
|
||||
❌ "No items"
|
||||
✅ "No projects yet. Create your first project to get started."
|
||||
[+ Create Project]
|
||||
```
|
||||
|
||||
**Match the tone.** A todo app empty state can be playful; an enterprise dashboard should be professional.
|
||||
|
||||
---
|
||||
|
||||
## Form Design
|
||||
|
||||
### Input Sizing
|
||||
|
||||
Match input width to expected content:
|
||||
- Email/URL: Full width or ~400px
|
||||
- Phone: ~200px
|
||||
- ZIP code: ~100px
|
||||
- Street address: Full width
|
||||
- City: ~200px
|
||||
- State dropdown: ~150px
|
||||
|
||||
### Placeholder vs. Label
|
||||
|
||||
**Never use placeholder as the only label.** Placeholders disappear on focus. Always have a visible label.
|
||||
|
||||
Placeholders work for:
|
||||
- Format hints: "MM/DD/YYYY"
|
||||
- Examples: "e.g., john@example.com"
|
||||
- Optional clarification
|
||||
|
||||
### Input States
|
||||
|
||||
```
|
||||
Default: border-gray-300
|
||||
Focus: border-blue-500 ring-2 ring-blue-200
|
||||
Error: border-red-500 ring-2 ring-red-200
|
||||
Disabled: bg-gray-100 text-gray-400
|
||||
Success: border-green-500 (sparingly)
|
||||
```
|
||||
|
||||
### Button Hierarchy
|
||||
|
||||
One primary action per view. Everything else is secondary or tertiary.
|
||||
|
||||
```
|
||||
Primary: Solid color, high contrast (bg-blue-600 text-white)
|
||||
Secondary: Outlined or muted (border border-gray-300)
|
||||
Tertiary: Text only (text-blue-600 hover:underline)
|
||||
Danger: Red but not screaming (bg-red-600 for confirm, text-red-600 for trigger)
|
||||
```
|
||||
|
||||
### Form Layout
|
||||
|
||||
- One column for simple forms
|
||||
- Two columns ONLY when inputs are related (First/Last name, City/State)
|
||||
- Labels above inputs on mobile, beside on desktop (optional)
|
||||
- Group related fields with subtle boundaries or spacing
|
||||
|
||||
---
|
||||
|
||||
## Image Treatment
|
||||
|
||||
### Background Images
|
||||
|
||||
**Problem:** Text over images is often unreadable.
|
||||
|
||||
**Solutions:**
|
||||
1. Semi-transparent overlay: `bg-black/50`
|
||||
2. Gradient overlay: `bg-gradient-to-t from-black/80 to-transparent`
|
||||
3. Text shadow: `text-shadow: 0 2px 4px rgba(0,0,0,0.5)`
|
||||
4. Solid color box behind text
|
||||
5. Choose images with natural dark/simple areas for text
|
||||
|
||||
### User Avatars
|
||||
|
||||
- Always have a fallback (initials, generic icon)
|
||||
- Consistent size per context (32px list, 48px card, 96px profile)
|
||||
- Round for people, square with border-radius for companies/products
|
||||
- Border adds polish: `ring-2 ring-white` for overlapping avatars
|
||||
|
||||
### Hero Images
|
||||
|
||||
- Don't stretch—use `object-cover`
|
||||
- Consider `aspect-ratio` for consistency
|
||||
- Compress appropriately (WebP, quality 80%)
|
||||
|
||||
---
|
||||
|
||||
## Icon Usage
|
||||
|
||||
### Sizing
|
||||
|
||||
Icons should feel balanced with adjacent text:
|
||||
- 12-14px text: 16px icon
|
||||
- 16px text: 20px icon
|
||||
- 18-20px text: 24px icon
|
||||
|
||||
### Icon + Text Pairing
|
||||
|
||||
Always align icon center with text baseline or center. Add consistent gap (8px typical).
|
||||
|
||||
```html
|
||||
<span class="flex items-center gap-2">
|
||||
<IconSettings class="w-5 h-5" />
|
||||
<span>Settings</span>
|
||||
</span>
|
||||
```
|
||||
|
||||
### When to Use Icons
|
||||
|
||||
- Navigation items
|
||||
- Common actions (edit, delete, share)
|
||||
- Status indicators
|
||||
- Feature lists (with caution—don't overdo)
|
||||
|
||||
### When NOT to Use Icons
|
||||
|
||||
- Don't add icons just to fill space
|
||||
- Skip icons on buttons with clear text ("Submit", "Continue")
|
||||
- Avoid decorative-only icons that add no meaning
|
||||
|
||||
---
|
||||
|
||||
## Interaction States
|
||||
|
||||
Every interactive element needs visible state changes:
|
||||
|
||||
### Hover
|
||||
- Subtle background change
|
||||
- Slight shadow increase
|
||||
- Color shift (darken primary by 10%)
|
||||
|
||||
### Active/Pressed
|
||||
- Darker than hover
|
||||
- Slight scale down (`scale-95`)
|
||||
- Reduce shadow
|
||||
|
||||
### Focus
|
||||
- Obvious ring (critical for accessibility)
|
||||
- Don't rely on color alone
|
||||
- `focus-visible` for keyboard-only focus
|
||||
|
||||
### Loading
|
||||
- Disable interaction
|
||||
- Show spinner or skeleton
|
||||
- Maintain layout size (prevent shift)
|
||||
|
||||
```css
|
||||
/* Example button states */
|
||||
.btn {
|
||||
@apply bg-blue-600 hover:bg-blue-700 active:bg-blue-800;
|
||||
@apply focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2;
|
||||
@apply disabled:opacity-50 disabled:cursor-not-allowed;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color Psychology
|
||||
|
||||
Use color purposefully:
|
||||
|
||||
| Color | Association | Use for |
|
||||
|-------|-------------|---------|
|
||||
| Blue | Trust, calm, professional | Primary actions, links, corporate |
|
||||
| Green | Success, growth, go | Success states, positive actions |
|
||||
| Red | Error, danger, urgency | Errors, destructive actions, alerts |
|
||||
| Yellow/Orange | Warning, attention | Warnings, highlights |
|
||||
| Purple | Premium, creative | Premium features, creative apps |
|
||||
| Gray | Neutral, professional | Text, backgrounds, borders |
|
||||
|
||||
### Avoid
|
||||
|
||||
- Red for non-destructive primary buttons
|
||||
- Green for errors (colorblind users)
|
||||
- Low-saturation colors for important actions
|
||||
- More than 3 accent colors per interface
|
||||
|
||||
---
|
||||
|
||||
## Border Radius System
|
||||
|
||||
Stay consistent. Pick a system:
|
||||
|
||||
**Sharp/Modern:**
|
||||
```
|
||||
none: 0
|
||||
sm: 2px
|
||||
md: 4px
|
||||
lg: 6px
|
||||
full: 9999px (pills/circles)
|
||||
```
|
||||
|
||||
**Soft/Friendly:**
|
||||
```
|
||||
none: 0
|
||||
sm: 4px
|
||||
md: 8px
|
||||
lg: 12px
|
||||
xl: 16px
|
||||
full: 9999px
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- Nested elements: inner radius = outer radius - padding
|
||||
- Small elements get smaller radius (badges, tags)
|
||||
- Large elements can have larger radius (cards, modals)
|
||||
- Images inside cards: match card radius or use `overflow-hidden`
|
||||
|
||||
---
|
||||
|
||||
## Text Wrapping & Truncation
|
||||
|
||||
### When to Truncate
|
||||
|
||||
- Navigation items
|
||||
- Table cells with fixed widths
|
||||
- Card titles (with hover to reveal)
|
||||
|
||||
```css
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
```
|
||||
|
||||
### When NOT to Truncate
|
||||
|
||||
- Body text
|
||||
- Important information
|
||||
- Search results
|
||||
- Error messages
|
||||
|
||||
### Multi-line Truncation
|
||||
|
||||
```css
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Responsive Breakpoints
|
||||
|
||||
Standard breakpoints (Tailwind default):
|
||||
|
||||
```
|
||||
sm: 640px (landscape phones)
|
||||
md: 768px (tablets)
|
||||
lg: 1024px (laptops)
|
||||
xl: 1280px (desktops)
|
||||
2xl: 1536px (large screens)
|
||||
```
|
||||
|
||||
### Mobile-First Principles
|
||||
|
||||
1. Design for mobile first, add complexity for larger screens
|
||||
2. Stack on mobile, side-by-side on desktop
|
||||
3. Full-width inputs on mobile, constrained on desktop
|
||||
4. Larger touch targets on mobile (44px minimum)
|
||||
5. Hide secondary navigation in hamburger on mobile
|
||||
|
||||
---
|
||||
|
||||
## Modal and Overlay Patterns
|
||||
|
||||
### Modal Sizing
|
||||
|
||||
| Content Type | Width | Height |
|
||||
|--------------|-------|--------|
|
||||
| Confirmation dialog | 400-500px | Auto (minimal) |
|
||||
| Form modal | 500-600px | Auto |
|
||||
| Content modal | 600-800px | 70-80vh max |
|
||||
| Full-screen modal | 100vw | 100vh |
|
||||
|
||||
### Modal Structure
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Title ✕ Close │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Modal content here │
|
||||
│ │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Cancel Primary Action│
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Backdrop
|
||||
|
||||
```css
|
||||
.backdrop {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
/* Or with blur */
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
```
|
||||
|
||||
### Modal Transitions
|
||||
|
||||
```css
|
||||
/* Fade + Scale */
|
||||
.modal {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
transition: opacity 200ms ease-out, transform 200ms ease-out;
|
||||
}
|
||||
.modal.open {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dropdown and Menu Design
|
||||
|
||||
### Dropdown Positioning
|
||||
|
||||
- Default: Below trigger, left-aligned
|
||||
- Flip: Above if no space below
|
||||
- Constrain to viewport
|
||||
|
||||
```css
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
```
|
||||
|
||||
### Menu Styling
|
||||
|
||||
```css
|
||||
.menu {
|
||||
min-width: 180px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.menu-divider {
|
||||
height: 1px;
|
||||
background: #e5e7eb;
|
||||
margin: 4px 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Menu Item Types
|
||||
|
||||
| Type | Visual |
|
||||
|------|--------|
|
||||
| Standard | Text label |
|
||||
| With icon | Icon + Label |
|
||||
| With shortcut | Label + Shortcut |
|
||||
| With description | Label + Description |
|
||||
| Destructive | Red text |
|
||||
| Disabled | Grayed out |
|
||||
|
||||
---
|
||||
|
||||
## Navigation Patterns
|
||||
|
||||
### Top Navigation
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Logo Nav Item Nav Item Nav Item CTA │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- Sticky on scroll (optional)
|
||||
- Collapse to hamburger on mobile
|
||||
- Clear active state
|
||||
|
||||
### Side Navigation
|
||||
|
||||
```
|
||||
┌──────────────┬──────────────────────────────────┐
|
||||
│ Logo │ │
|
||||
│ │ │
|
||||
│ Dashboard │ Main Content │
|
||||
│ Projects │ │
|
||||
│ Settings │ │
|
||||
│ │ │
|
||||
│ ───────── │ │
|
||||
│ Account │ │
|
||||
│ Logout │ │
|
||||
└──────────────┴──────────────────────────────────┘
|
||||
```
|
||||
|
||||
- Collapsible to icons on desktop
|
||||
- Full overlay on mobile
|
||||
- Group related items
|
||||
|
||||
### Breadcrumbs
|
||||
|
||||
```
|
||||
Home > Category > Subcategory > Current Page
|
||||
```
|
||||
|
||||
- Clickable except current page
|
||||
- Truncate middle items if too long
|
||||
- Show on detail/nested pages
|
||||
|
||||
### Tabs
|
||||
|
||||
```css
|
||||
.tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
border-bottom-color: #3b82f6;
|
||||
color: #3b82f6;
|
||||
}
|
||||
```
|
||||
|
||||
- Underline or pill style
|
||||
- Horizontal scroll on mobile if many tabs
|
||||
- Consider vertical tabs for settings
|
||||
@@ -0,0 +1,428 @@
|
||||
# Animation & Microinteractions
|
||||
|
||||
Guidelines for when and how to animate UI elements effectively.
|
||||
|
||||
## The Purpose of Animation
|
||||
|
||||
Animation should serve a function, not just look nice.
|
||||
|
||||
### Valid Reasons to Animate
|
||||
|
||||
| Purpose | Example |
|
||||
|---------|---------|
|
||||
| **Feedback** | Button press confirmation, form submission success |
|
||||
| **Orientation** | Showing where something came from or went to |
|
||||
| **Focus** | Drawing attention to important changes |
|
||||
| **Teaching** | Demonstrating how something works |
|
||||
| **Continuity** | Maintaining context during state changes |
|
||||
| **Delight** | Occasional surprise (use sparingly) |
|
||||
|
||||
### Invalid Reasons to Animate
|
||||
|
||||
- "It looks cool"
|
||||
- "The competition does it"
|
||||
- "To show off our skills"
|
||||
- Every state change
|
||||
- To hide slow performance
|
||||
|
||||
---
|
||||
|
||||
## Timing & Duration
|
||||
|
||||
### Duration Guidelines
|
||||
|
||||
| Animation Type | Duration | Rationale |
|
||||
|----------------|----------|-----------|
|
||||
| Micro-feedback (hover, press) | 100-150ms | Must feel instant |
|
||||
| Simple transitions (fade, slide) | 150-250ms | Noticeable but quick |
|
||||
| Complex transitions (modal, navigation) | 250-350ms | Need time to follow |
|
||||
| Entrances/Reveals | 200-400ms | Can be slightly longer |
|
||||
| Decorative/Emphasis | 300-500ms | Purpose is to be noticed |
|
||||
|
||||
### The 200ms Rule
|
||||
|
||||
Most UI animations should be around 200ms:
|
||||
- Faster than 100ms → Too fast to perceive
|
||||
- Slower than 400ms → Feels sluggish, interrupts flow
|
||||
|
||||
**Exception:** Loading and progress indicators can be slower because they represent real waiting.
|
||||
|
||||
### Duration by Distance
|
||||
|
||||
Longer travel distance = longer duration (but not proportionally).
|
||||
|
||||
```
|
||||
Small movement (8-16px): 100-150ms
|
||||
Medium movement (50-100px): 150-250ms
|
||||
Large movement (full screen): 250-350ms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Easing Functions
|
||||
|
||||
Easing makes motion feel natural. Linear motion looks robotic.
|
||||
|
||||
### Common Easing Curves
|
||||
|
||||
| Easing | Use For | Feel |
|
||||
|--------|---------|------|
|
||||
| **ease-out** | Elements entering | Fast start, gentle stop |
|
||||
| **ease-in** | Elements leaving | Gentle start, fast exit |
|
||||
| **ease-in-out** | Elements moving within view | Smooth throughout |
|
||||
| **linear** | Progress indicators, opacity changes | Mechanical (intentional) |
|
||||
|
||||
### When to Use Each
|
||||
|
||||
**Ease-out (default for entrances):**
|
||||
```css
|
||||
transition: transform 200ms ease-out;
|
||||
```
|
||||
- Modals appearing
|
||||
- Notifications sliding in
|
||||
- Dropdowns opening
|
||||
- Tooltips appearing
|
||||
|
||||
**Ease-in (for exits):**
|
||||
```css
|
||||
transition: opacity 150ms ease-in;
|
||||
```
|
||||
- Modals dismissing
|
||||
- Elements fading out
|
||||
- Notifications leaving
|
||||
|
||||
**Ease-in-out (for on-screen movement):**
|
||||
```css
|
||||
transition: transform 250ms ease-in-out;
|
||||
```
|
||||
- Tab indicators sliding
|
||||
- Carousel transitions
|
||||
- Drawer/sidebar toggling
|
||||
|
||||
### Custom Cubic Bezier
|
||||
|
||||
For more personality, customize curves:
|
||||
|
||||
```css
|
||||
/* Snappy entrance */
|
||||
transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
|
||||
/* Smooth overshoot */
|
||||
transition: transform 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Animation Patterns
|
||||
|
||||
### Button States
|
||||
|
||||
**Hover:**
|
||||
```css
|
||||
.btn {
|
||||
transition: background-color 100ms ease-out, transform 100ms ease-out;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: var(--btn-hover);
|
||||
}
|
||||
```
|
||||
|
||||
**Active/Pressed:**
|
||||
```css
|
||||
.btn:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
```
|
||||
|
||||
**Loading state:**
|
||||
```css
|
||||
.btn.loading {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
.btn.loading .spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
```
|
||||
|
||||
### Modal Entrance/Exit
|
||||
|
||||
**Enter:**
|
||||
```css
|
||||
.modal {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(-10px);
|
||||
transition: opacity 200ms ease-out, transform 200ms ease-out;
|
||||
}
|
||||
.modal.open {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
```
|
||||
|
||||
**Exit:**
|
||||
```css
|
||||
.modal.closing {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
transition: opacity 150ms ease-in, transform 150ms ease-in;
|
||||
}
|
||||
```
|
||||
|
||||
### Dropdown/Menu
|
||||
|
||||
```css
|
||||
.dropdown {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
pointer-events: none;
|
||||
transition: opacity 150ms ease-out, transform 150ms ease-out;
|
||||
}
|
||||
.dropdown.open {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: auto;
|
||||
}
|
||||
```
|
||||
|
||||
### Toast/Notification
|
||||
|
||||
```css
|
||||
.toast {
|
||||
transform: translateX(100%);
|
||||
transition: transform 300ms ease-out;
|
||||
}
|
||||
.toast.visible {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.toast.exiting {
|
||||
transform: translateX(100%);
|
||||
transition: transform 200ms ease-in;
|
||||
}
|
||||
```
|
||||
|
||||
### Skeleton Loading
|
||||
|
||||
```css
|
||||
.skeleton {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#f0f0f0 25%,
|
||||
#e0e0e0 50%,
|
||||
#f0f0f0 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Loading States
|
||||
|
||||
### Types of Loading Indicators
|
||||
|
||||
| Type | Use When | Example |
|
||||
|------|----------|---------|
|
||||
| **Spinner** | Unknown duration, short wait expected | Button submission |
|
||||
| **Progress bar** | Known progress, longer operations | File upload |
|
||||
| **Skeleton** | Loading content layout | Feed items |
|
||||
| **Pulse/Shimmer** | Refreshing existing content | Pull to refresh |
|
||||
|
||||
### Spinner Guidelines
|
||||
|
||||
- Don't show immediately (wait 300-500ms)
|
||||
- If wait < 1 second, spinner may not be needed
|
||||
- Position in context (where content will appear)
|
||||
- Provide cancel option for long operations
|
||||
|
||||
```jsx
|
||||
// Delay spinner to avoid flash for fast operations
|
||||
const [showSpinner, setShowSpinner] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
const timer = setTimeout(() => setShowSpinner(true), 400);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
setShowSpinner(false);
|
||||
}, [isLoading]);
|
||||
```
|
||||
|
||||
### Progress Bar Guidelines
|
||||
|
||||
- Show percentage when meaningful
|
||||
- Don't let it jump backwards
|
||||
- Consider indeterminate state if progress unknown
|
||||
- Complete to 100% before hiding
|
||||
|
||||
### Skeleton Screen Guidelines
|
||||
|
||||
- Match layout of actual content
|
||||
- Use consistent bone shapes
|
||||
- Animate subtly (shimmer, not bounce)
|
||||
- Replace with content immediately when loaded
|
||||
|
||||
---
|
||||
|
||||
## Microinteractions
|
||||
|
||||
Small animations that provide feedback and delight.
|
||||
|
||||
### Effective Microinteractions
|
||||
|
||||
**Toggle switches:**
|
||||
```css
|
||||
.toggle-thumb {
|
||||
transition: transform 150ms ease-out;
|
||||
}
|
||||
.toggle.on .toggle-thumb {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
```
|
||||
|
||||
**Checkbox check:**
|
||||
```css
|
||||
.checkmark {
|
||||
stroke-dasharray: 20;
|
||||
stroke-dashoffset: 20;
|
||||
transition: stroke-dashoffset 200ms ease-out;
|
||||
}
|
||||
.checkbox.checked .checkmark {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Like/Heart animation:**
|
||||
```css
|
||||
.heart {
|
||||
transform: scale(1);
|
||||
transition: transform 150ms ease-out;
|
||||
}
|
||||
.heart.liked {
|
||||
animation: pop 300ms ease-out;
|
||||
}
|
||||
@keyframes pop {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.2); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
```
|
||||
|
||||
**Input focus:**
|
||||
```css
|
||||
.input {
|
||||
border-color: #ccc;
|
||||
transition: border-color 150ms ease-out, box-shadow 150ms ease-out;
|
||||
}
|
||||
.input:focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
```
|
||||
|
||||
### When Microinteractions Help
|
||||
|
||||
- Confirming user action occurred
|
||||
- Showing state change clearly
|
||||
- Making interface feel responsive
|
||||
- Guiding attention to changes
|
||||
|
||||
### When to Skip
|
||||
|
||||
- Repetitive actions (every keystroke)
|
||||
- Performance-critical paths
|
||||
- Accessibility mode (respect reduce-motion)
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Considerations
|
||||
|
||||
### Respect User Preferences
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Provide Alternatives
|
||||
|
||||
- Don't rely on animation alone to convey information
|
||||
- Ensure state changes are visible without animation
|
||||
- Allow users to disable animations in app settings
|
||||
|
||||
### Avoid Problematic Animations
|
||||
|
||||
| Avoid | Reason |
|
||||
|-------|--------|
|
||||
| Flashing/strobing | Can trigger seizures |
|
||||
| Parallax scrolling | Causes motion sickness |
|
||||
| Auto-playing video | Distracting, accessibility |
|
||||
| Infinite loops | Drains attention, battery |
|
||||
|
||||
---
|
||||
|
||||
## Performance Guidelines
|
||||
|
||||
### GPU-Accelerated Properties
|
||||
|
||||
Animate these for smooth 60fps:
|
||||
- `transform` (translate, scale, rotate)
|
||||
- `opacity`
|
||||
|
||||
Avoid animating (causes reflow/repaint):
|
||||
- `width`, `height`
|
||||
- `top`, `left`, `right`, `bottom`
|
||||
- `margin`, `padding`
|
||||
- `border-width`
|
||||
- `font-size`
|
||||
|
||||
### Use will-change Sparingly
|
||||
|
||||
```css
|
||||
/* Only for elements about to animate */
|
||||
.modal {
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
/* Remove after animation */
|
||||
.modal.static {
|
||||
will-change: auto;
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Animations
|
||||
|
||||
Start animations together, not staggered excessively:
|
||||
- 0-50ms stagger: feels cohesive
|
||||
- 100ms+ stagger: feels slow, sequential
|
||||
|
||||
### Test on Low-end Devices
|
||||
|
||||
What's smooth on your MacBook may stutter on a budget Android phone. Test on real devices or throttle CPU in DevTools.
|
||||
|
||||
---
|
||||
|
||||
## Animation Checklist
|
||||
|
||||
Before shipping an animation:
|
||||
|
||||
- [ ] Does it serve a purpose (not just decoration)?
|
||||
- [ ] Is duration appropriate for the action?
|
||||
- [ ] Does easing feel natural?
|
||||
- [ ] Does it work with `prefers-reduced-motion`?
|
||||
- [ ] Is it GPU-accelerated (transform/opacity)?
|
||||
- [ ] Does it perform well on low-end devices?
|
||||
- [ ] Can the interface function without it?
|
||||
@@ -0,0 +1,472 @@
|
||||
# Data Visualization Design
|
||||
|
||||
Practical guide to designing charts, tables, and data displays that communicate clearly.
|
||||
|
||||
## Chart Selection
|
||||
|
||||
### Choosing the Right Chart Type
|
||||
|
||||
| Data Type | Best Chart | Avoid |
|
||||
|-----------|-----------|-------|
|
||||
| Part-to-whole | Pie (≤5 slices), stacked bar | Pie with many slices |
|
||||
| Change over time | Line chart, area chart | Pie chart |
|
||||
| Comparison (few items) | Bar chart (horizontal or vertical) | Line chart |
|
||||
| Comparison (many items) | Horizontal bar chart | Vertical bars (labels hard) |
|
||||
| Distribution | Histogram, box plot | Pie chart |
|
||||
| Correlation | Scatter plot | Line chart |
|
||||
| Composition over time | Stacked area, stacked bar | Multiple pie charts |
|
||||
|
||||
### When NOT to Use a Chart
|
||||
|
||||
Sometimes a simple number is better:
|
||||
- Single data point → Big number display
|
||||
- Two numbers to compare → Side-by-side with % change
|
||||
- Very few data points → Table might be clearer
|
||||
|
||||
```
|
||||
❌ Pie chart with 2 slices (73% vs 27%)
|
||||
✅ "73% of users completed onboarding"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bar Charts
|
||||
|
||||
### Orientation
|
||||
|
||||
**Vertical bars:** Best when comparing few categories with short labels
|
||||
**Horizontal bars:** Best when comparing many categories or labels are long
|
||||
|
||||
### Bar Chart Rules
|
||||
|
||||
1. **Always start at zero** - Truncated axes mislead
|
||||
2. **Order meaningfully** - By value (descending) or logical order (time, alphabet)
|
||||
3. **Space bars correctly** - Gap between bars = 50-100% of bar width
|
||||
4. **Label directly** - Put values on bars, not in legend
|
||||
5. **Limit categories** - 5-7 bars maximum; group others as "Other"
|
||||
|
||||
### Bar Styling
|
||||
|
||||
```css
|
||||
/* Bar appearance */
|
||||
.bar {
|
||||
fill: var(--primary);
|
||||
border-radius: 2px 2px 0 0; /* Slight rounding on top only */
|
||||
}
|
||||
|
||||
/* Hover state */
|
||||
.bar:hover {
|
||||
fill: var(--primary-dark);
|
||||
}
|
||||
|
||||
/* Spacing */
|
||||
.bar-gap: 8px;
|
||||
.bar-width: 32px;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Line Charts
|
||||
|
||||
### When to Use
|
||||
|
||||
- Continuous data over time
|
||||
- Trends matter more than individual values
|
||||
- Comparing multiple series over same time period
|
||||
|
||||
### Line Chart Rules
|
||||
|
||||
1. **Start Y-axis at zero** (usually) - Unless all values are in narrow range
|
||||
2. **Limit to 4-5 lines** - More becomes unreadable
|
||||
3. **Use distinct line styles** - Different colors, consider dashes for printing
|
||||
4. **Label lines directly** - Not just in legend
|
||||
5. **Highlight important points** - Mark significant events or thresholds
|
||||
|
||||
### Line Styling
|
||||
|
||||
```css
|
||||
/* Line appearance */
|
||||
.line {
|
||||
stroke-width: 2px;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
/* Data points */
|
||||
.data-point {
|
||||
r: 4px;
|
||||
fill: white;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
/* Only show points on hover or for few data points */
|
||||
.data-point { opacity: 0; }
|
||||
.line-group:hover .data-point { opacity: 1; }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pie Charts
|
||||
|
||||
### When to Use (Rarely)
|
||||
|
||||
- Showing parts of a whole
|
||||
- 2-5 slices maximum
|
||||
- Exact values less important than proportions
|
||||
- Users understand percentages must sum to 100%
|
||||
|
||||
### When NOT to Use
|
||||
|
||||
- Comparing values (use bar chart)
|
||||
- More than 5 categories
|
||||
- Values that don't sum to 100%
|
||||
- Showing change over time
|
||||
|
||||
### Pie Chart Rules
|
||||
|
||||
1. **Limit slices** - Maximum 5; combine small values into "Other"
|
||||
2. **Order by size** - Largest slice starting at 12 o'clock
|
||||
3. **Label directly** - Put labels on or near slices, not in legend
|
||||
4. **Show percentages** - Values help interpretation
|
||||
5. **Consider donut** - Center can show total or key metric
|
||||
|
||||
### Alternative: Simple Numbers
|
||||
|
||||
Often clearer than a pie chart:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 67% Mobile 23% Desktop 10% Tablet │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color in Data Visualization
|
||||
|
||||
### Categorical Colors
|
||||
|
||||
For different categories, use distinct hues:
|
||||
|
||||
```
|
||||
Blue: #3b82f6
|
||||
Orange: #f97316
|
||||
Green: #22c55e
|
||||
Purple: #8b5cf6
|
||||
Yellow: #eab308
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Maximum 5-7 distinct colors
|
||||
- Avoid red/green together (colorblindness)
|
||||
- Test with colorblind simulator
|
||||
|
||||
### Sequential Colors
|
||||
|
||||
For continuous data (low to high), use single hue with varying lightness:
|
||||
|
||||
```
|
||||
Light: #dbeafe (low values)
|
||||
#93c5fd
|
||||
#60a5fa
|
||||
#3b82f6
|
||||
Dark: #1d4ed8 (high values)
|
||||
```
|
||||
|
||||
### Diverging Colors
|
||||
|
||||
For data with meaningful center (positive/negative, above/below average):
|
||||
|
||||
```
|
||||
Negative: #ef4444 (red)
|
||||
Neutral: #f3f4f6 (gray)
|
||||
Positive: #22c55e (green)
|
||||
```
|
||||
|
||||
### Colorblind-Safe Palettes
|
||||
|
||||
Test your palette with:
|
||||
- Coblis Color Blindness Simulator
|
||||
- Chrome DevTools rendering settings
|
||||
|
||||
**Safe combinations:**
|
||||
- Blue + Orange (deuteranopia safe)
|
||||
- Blue + Yellow
|
||||
- Purple + Green + Orange
|
||||
|
||||
---
|
||||
|
||||
## Tables
|
||||
|
||||
### When Tables Beat Charts
|
||||
|
||||
- Precise values matter
|
||||
- Users need to look up specific data
|
||||
- Multiple attributes per item
|
||||
- Data will be exported/copied
|
||||
|
||||
### Table Design Principles
|
||||
|
||||
**1. Align numbers right**
|
||||
```
|
||||
Revenue
|
||||
────────
|
||||
$1,234
|
||||
$12,456
|
||||
$123,789
|
||||
```
|
||||
|
||||
**2. Align text left**
|
||||
```
|
||||
Product Name
|
||||
────────────
|
||||
Widget A
|
||||
Premium Widget
|
||||
Widget Pro Max
|
||||
```
|
||||
|
||||
**3. Use consistent precision**
|
||||
```
|
||||
❌ $1234, $567.89, $2.5k
|
||||
✅ $1,234.00, $567.89, $2,500.00
|
||||
```
|
||||
|
||||
**4. Minimize borders**
|
||||
```css
|
||||
/* Light horizontal lines only */
|
||||
tr {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
/* No vertical borders, no heavy lines */
|
||||
```
|
||||
|
||||
**5. Generous padding**
|
||||
```css
|
||||
td {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
```
|
||||
|
||||
### Table Patterns
|
||||
|
||||
**Zebra striping:**
|
||||
```css
|
||||
tr:nth-child(even) {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
```
|
||||
|
||||
**Hover highlighting:**
|
||||
```css
|
||||
tr:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
```
|
||||
|
||||
**Sortable columns:**
|
||||
```html
|
||||
<th>
|
||||
Revenue
|
||||
<button aria-label="Sort by revenue">▼</button>
|
||||
</th>
|
||||
```
|
||||
|
||||
**Sticky headers:**
|
||||
```css
|
||||
thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dashboard Layout
|
||||
|
||||
### Visual Hierarchy
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ KEY METRICS (big numbers) │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ $45.2K │ │ 1,234 │ │ 78.3% │ │ 4.2 │ │
|
||||
│ │ Revenue │ │ Users │ │ Conv. │ │ Rating │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
├──────────────────────────────────────────────────────┤
|
||||
│ PRIMARY CHART (most important trend) │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ [Line chart: Revenue over time] │ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
├──────────────────────────┬───────────────────────────┤
|
||||
│ SUPPORTING CHARTS │ DETAIL TABLE │
|
||||
│ ┌──────────────────┐ │ ┌───────────────────────┐ │
|
||||
│ │ [Bar chart] │ │ │ Recent Transactions │ │
|
||||
│ └──────────────────┘ │ │ ... │ │
|
||||
│ ┌──────────────────┐ │ │ ... │ │
|
||||
│ │ [Donut chart] │ │ └───────────────────────┘ │
|
||||
│ └──────────────────┘ │ │
|
||||
└──────────────────────────┴───────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Metric Cards
|
||||
|
||||
```html
|
||||
<div class="metric-card">
|
||||
<span class="label">Revenue</span>
|
||||
<span class="value">$45,234</span>
|
||||
<span class="change positive">↑ 12.3%</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.metric-card {
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.change.positive { color: #22c55e; }
|
||||
.change.negative { color: #ef4444; }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chart Annotations
|
||||
|
||||
### When to Annotate
|
||||
|
||||
- Explain anomalies
|
||||
- Mark significant events
|
||||
- Highlight thresholds
|
||||
- Call out key insights
|
||||
|
||||
### Annotation Patterns
|
||||
|
||||
**Event markers:**
|
||||
```
|
||||
╭─── Product launch
|
||||
Revenue ──────────/│\──────────────
|
||||
│
|
||||
```
|
||||
|
||||
**Threshold lines:**
|
||||
```
|
||||
Revenue ──────────────────────
|
||||
- - - - - Target: $50k - - - -
|
||||
──────────────────────────────
|
||||
```
|
||||
|
||||
**Callout boxes:**
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ 📈 Best month ever! │
|
||||
│ +47% vs. last year │
|
||||
└─────────────┬────────────┘
|
||||
│
|
||||
▼
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility in Data Viz
|
||||
|
||||
### Color Considerations
|
||||
|
||||
- Don't rely on color alone
|
||||
- Add patterns, labels, or icons
|
||||
- Ensure sufficient contrast
|
||||
- Test with colorblind simulators
|
||||
|
||||
### Screen Reader Support
|
||||
|
||||
```html
|
||||
<figure role="img" aria-label="Bar chart showing monthly revenue">
|
||||
<figcaption class="sr-only">
|
||||
Revenue by month: January $10,000, February $12,000...
|
||||
</figcaption>
|
||||
<svg>...</svg>
|
||||
</figure>
|
||||
```
|
||||
|
||||
### Data Tables as Alternative
|
||||
|
||||
For complex charts, provide data table version:
|
||||
```html
|
||||
<details>
|
||||
<summary>View data as table</summary>
|
||||
<table>...</table>
|
||||
</details>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
| Mistake | Problem | Fix |
|
||||
|---------|---------|-----|
|
||||
| 3D charts | Distorts perception | Use flat 2D |
|
||||
| Dual Y-axes | Confusing comparisons | Separate charts |
|
||||
| Too many colors | Visual noise | Limit to 5-7 |
|
||||
| Missing zero | Misleading differences | Start at zero |
|
||||
| No labels | Hard to interpret | Label directly |
|
||||
| Decorative elements | Chartjunk distracts | Remove non-data ink |
|
||||
| Inconsistent scales | Misleading comparison | Match scales |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Chart Decision Tree
|
||||
|
||||
```
|
||||
What do you want to show?
|
||||
│
|
||||
├─ Comparison → Bar chart
|
||||
│
|
||||
├─ Change over time → Line chart
|
||||
│
|
||||
├─ Part of whole → Pie (≤5) or stacked bar
|
||||
│
|
||||
├─ Distribution → Histogram
|
||||
│
|
||||
├─ Relationship → Scatter plot
|
||||
│
|
||||
└─ Single value → Big number
|
||||
```
|
||||
|
||||
### Minimum Viable Chart Styling
|
||||
|
||||
```css
|
||||
/* Axes */
|
||||
.axis line, .axis path {
|
||||
stroke: #e5e7eb;
|
||||
}
|
||||
.axis text {
|
||||
fill: #6b7280;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
.grid line {
|
||||
stroke: #f3f4f6;
|
||||
}
|
||||
|
||||
/* Data */
|
||||
.bar, .line, .point {
|
||||
fill: #3b82f6;
|
||||
stroke: #3b82f6;
|
||||
}
|
||||
|
||||
/* Labels */
|
||||
.label {
|
||||
fill: #374151;
|
||||
font-size: 11px;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,446 @@
|
||||
# Theming & Dark Mode Design
|
||||
|
||||
Creating effective color themes and implementing dark mode correctly.
|
||||
|
||||
## Dark Mode Philosophy
|
||||
|
||||
Dark mode isn't just inverting colors—it requires deliberate design decisions to maintain usability, hierarchy, and aesthetics.
|
||||
|
||||
### Why Dark Mode Matters
|
||||
|
||||
- **User preference:** Many users prefer it
|
||||
- **Eye strain:** Reduces strain in low-light environments
|
||||
- **Battery life:** Saves power on OLED screens
|
||||
- **Accessibility:** Some users have photosensitivity
|
||||
- **Professional expectation:** Users expect modern apps to support it
|
||||
|
||||
---
|
||||
|
||||
## Dark Mode Color Principles
|
||||
|
||||
### Don't Just Invert
|
||||
|
||||
| Light Mode | Bad Dark Mode | Good Dark Mode |
|
||||
|------------|---------------|----------------|
|
||||
| White `#ffffff` | Black `#000000` | Dark gray `#18181b` |
|
||||
| Black text `#000000` | White text `#ffffff` | Off-white `#fafafa` |
|
||||
| Gray `#6b7280` | Gray `#6b7280` | Lighter gray `#a1a1aa` |
|
||||
|
||||
### Key Principles
|
||||
|
||||
**1. Use dark grays, not pure black**
|
||||
|
||||
Pure black (`#000000`) creates harsh contrast and "halation" (text appears to glow).
|
||||
|
||||
```css
|
||||
/* Background scale for dark mode */
|
||||
--bg-base: #09090b; /* Deepest background */
|
||||
--bg-subtle: #18181b; /* Cards, elevated surfaces */
|
||||
--bg-muted: #27272a; /* Hover states, inputs */
|
||||
--bg-emphasis: #3f3f46; /* Active states */
|
||||
```
|
||||
|
||||
**2. Reduce contrast slightly**
|
||||
|
||||
Max contrast in dark mode is harsher than in light mode.
|
||||
|
||||
```css
|
||||
/* Text colors for dark mode */
|
||||
--text-primary: #fafafa; /* ~95% white, not 100% */
|
||||
--text-secondary: #a1a1aa; /* Muted text */
|
||||
--text-tertiary: #71717a; /* Subtle text */
|
||||
```
|
||||
|
||||
**3. Desaturate colors**
|
||||
|
||||
Bright saturated colors on dark backgrounds cause eye strain.
|
||||
|
||||
```css
|
||||
/* Light mode brand color */
|
||||
--primary-light: #3b82f6; /* Bright blue */
|
||||
|
||||
/* Dark mode - slightly desaturated */
|
||||
--primary-dark: #60a5fa; /* Lighter, less saturated */
|
||||
```
|
||||
|
||||
**4. Elevate with lightness, not shadow**
|
||||
|
||||
In dark mode, shadows are invisible. Show elevation with lighter surfaces.
|
||||
|
||||
```css
|
||||
/* Light mode: shadow for depth */
|
||||
.card-light {
|
||||
background: white;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Dark mode: lighter surface for depth */
|
||||
.card-dark {
|
||||
background: #27272a; /* Lighter than base */
|
||||
box-shadow: none; /* Or very subtle */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building a Dark Mode Palette
|
||||
|
||||
### Step 1: Define Your Gray Scale
|
||||
|
||||
Create 9-10 shades from near-black to near-white:
|
||||
|
||||
```css
|
||||
/* Dark mode gray scale (Zinc example) */
|
||||
--gray-950: #09090b; /* Deepest background */
|
||||
--gray-900: #18181b; /* Card backgrounds */
|
||||
--gray-800: #27272a; /* Elevated surfaces */
|
||||
--gray-700: #3f3f46; /* Borders, dividers */
|
||||
--gray-600: #52525b; /* Disabled states */
|
||||
--gray-500: #71717a; /* Placeholder text */
|
||||
--gray-400: #a1a1aa; /* Secondary text */
|
||||
--gray-300: #d4d4d8; /* Primary text (alt) */
|
||||
--gray-200: #e4e4e7; /* Headings */
|
||||
--gray-100: #f4f4f5; /* Emphasis text */
|
||||
--gray-50: #fafafa; /* Primary text */
|
||||
```
|
||||
|
||||
### Step 2: Adjust Accent Colors
|
||||
|
||||
```css
|
||||
/* Primary color adjustments */
|
||||
/* Light mode: use 500-600 range */
|
||||
--primary-light: #2563eb;
|
||||
|
||||
/* Dark mode: use 400-500 range (lighter) */
|
||||
--primary-dark: #3b82f6;
|
||||
|
||||
/* Same for semantic colors */
|
||||
--success-light: #16a34a;
|
||||
--success-dark: #22c55e;
|
||||
|
||||
--error-light: #dc2626;
|
||||
--error-dark: #ef4444;
|
||||
```
|
||||
|
||||
### Step 3: Define Semantic Tokens
|
||||
|
||||
```css
|
||||
/* Semantic tokens that switch based on mode */
|
||||
:root {
|
||||
--color-bg: var(--gray-50);
|
||||
--color-bg-subtle: var(--gray-100);
|
||||
--color-text: var(--gray-900);
|
||||
--color-text-muted: var(--gray-600);
|
||||
--color-border: var(--gray-200);
|
||||
--color-primary: var(--blue-600);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--color-bg: var(--gray-950);
|
||||
--color-bg-subtle: var(--gray-900);
|
||||
--color-text: var(--gray-50);
|
||||
--color-text-muted: var(--gray-400);
|
||||
--color-border: var(--gray-800);
|
||||
--color-primary: var(--blue-400);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategies
|
||||
|
||||
### Strategy 1: CSS Custom Properties
|
||||
|
||||
```css
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--text: #18181b;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #18181b;
|
||||
--text: #fafafa;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
```
|
||||
|
||||
### Strategy 2: Data Attribute + Class
|
||||
|
||||
```html
|
||||
<html data-theme="dark">
|
||||
```
|
||||
|
||||
```css
|
||||
[data-theme="light"] {
|
||||
--bg: #ffffff;
|
||||
}
|
||||
[data-theme="dark"] {
|
||||
--bg: #18181b;
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Toggle theme
|
||||
function toggleTheme() {
|
||||
const current = document.documentElement.dataset.theme;
|
||||
document.documentElement.dataset.theme = current === 'dark' ? 'light' : 'dark';
|
||||
localStorage.setItem('theme', document.documentElement.dataset.theme);
|
||||
}
|
||||
|
||||
// Initialize from preference
|
||||
function initTheme() {
|
||||
const saved = localStorage.getItem('theme');
|
||||
const preferred = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
document.documentElement.dataset.theme = saved || preferred;
|
||||
}
|
||||
```
|
||||
|
||||
### Strategy 3: Tailwind Dark Mode
|
||||
|
||||
```html
|
||||
<!-- With class strategy -->
|
||||
<html class="dark">
|
||||
<body class="bg-white dark:bg-zinc-950 text-zinc-900 dark:text-zinc-50">
|
||||
```
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
darkMode: 'class', // or 'media' for system preference only
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Considerations
|
||||
|
||||
### Cards and Surfaces
|
||||
|
||||
```css
|
||||
/* Light: white with shadow */
|
||||
.card {
|
||||
background: white;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Dark: lighter surface, subtle or no shadow */
|
||||
[data-theme="dark"] .card {
|
||||
background: var(--gray-900);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.3); /* Darker shadow if any */
|
||||
/* Or: border: 1px solid var(--gray-800); */
|
||||
}
|
||||
```
|
||||
|
||||
### Form Inputs
|
||||
|
||||
```css
|
||||
.input {
|
||||
background: white;
|
||||
border: 1px solid var(--gray-300);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .input {
|
||||
background: var(--gray-900);
|
||||
border: 1px solid var(--gray-700);
|
||||
}
|
||||
```
|
||||
|
||||
### Buttons
|
||||
|
||||
```css
|
||||
/* Primary button */
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .btn-primary {
|
||||
/* Often same or slightly adjusted */
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
/* Secondary button */
|
||||
.btn-secondary {
|
||||
background: var(--gray-100);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .btn-secondary {
|
||||
background: var(--gray-800);
|
||||
color: var(--gray-100);
|
||||
}
|
||||
```
|
||||
|
||||
### Images and Media
|
||||
|
||||
```css
|
||||
/* Reduce brightness/contrast of images in dark mode */
|
||||
[data-theme="dark"] img:not([data-no-dim]) {
|
||||
filter: brightness(0.9) contrast(1.1);
|
||||
}
|
||||
|
||||
/* Invert diagrams/illustrations if needed */
|
||||
[data-theme="dark"] .diagram {
|
||||
filter: invert(1) hue-rotate(180deg);
|
||||
}
|
||||
```
|
||||
|
||||
### Syntax Highlighting
|
||||
|
||||
Don't forget code blocks need dark mode variants:
|
||||
- Use dark theme variants of syntax highlighters
|
||||
- Or invert colors appropriately
|
||||
- Popular: One Dark, Dracula, Night Owl
|
||||
|
||||
---
|
||||
|
||||
## Testing Dark Mode
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] All text is readable (sufficient contrast)
|
||||
- [ ] Hierarchy still clear (headings vs body)
|
||||
- [ ] Focus states visible
|
||||
- [ ] Images don't blow out
|
||||
- [ ] Forms inputs clearly visible
|
||||
- [ ] Error/success states distinct
|
||||
- [ ] Loading states visible
|
||||
- [ ] Shadows/elevation still work
|
||||
- [ ] Icons visible (may need color swap)
|
||||
- [ ] Brand colors still recognizable
|
||||
|
||||
### Contrast Ratios
|
||||
|
||||
Same WCAG requirements apply:
|
||||
- Normal text: 4.5:1 minimum
|
||||
- Large text: 3:1 minimum
|
||||
- UI components: 3:1 minimum
|
||||
|
||||
**Common dark mode fails:**
|
||||
- Gray text on dark background
|
||||
- Colored text on colored backgrounds
|
||||
- Disabled states too subtle
|
||||
|
||||
---
|
||||
|
||||
## Theme Toggle UI
|
||||
|
||||
### Placement
|
||||
|
||||
- Header/navigation (most common)
|
||||
- Settings page
|
||||
- Footer (less common)
|
||||
|
||||
### Icon Patterns
|
||||
|
||||
```html
|
||||
<!-- Sun/Moon toggle -->
|
||||
<button aria-label="Toggle dark mode">
|
||||
<svg class="sun hidden dark:block">...</svg>
|
||||
<svg class="moon block dark:hidden">...</svg>
|
||||
</button>
|
||||
```
|
||||
|
||||
### State Options
|
||||
|
||||
1. **Light / Dark** - Simple toggle
|
||||
2. **Light / Dark / System** - Respect OS preference option
|
||||
3. **Auto only** - Always follow system (no toggle)
|
||||
|
||||
### Persistence
|
||||
|
||||
```javascript
|
||||
// Save preference
|
||||
localStorage.setItem('theme', 'dark');
|
||||
|
||||
// Load preference (with system fallback)
|
||||
const theme = localStorage.getItem('theme') ||
|
||||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advanced: Multiple Themes
|
||||
|
||||
### Brand Themes
|
||||
|
||||
```css
|
||||
[data-theme="brand-a"] {
|
||||
--primary: #ff6b6b;
|
||||
--primary-hover: #ee5a5a;
|
||||
}
|
||||
|
||||
[data-theme="brand-b"] {
|
||||
--primary: #4ecdc4;
|
||||
--primary-hover: #3dbdb5;
|
||||
}
|
||||
```
|
||||
|
||||
### Theme Structure
|
||||
|
||||
```css
|
||||
/* Base tokens (don't change) */
|
||||
--spacing-4: 16px;
|
||||
--radius-md: 8px;
|
||||
|
||||
/* Color tokens (change per theme) */
|
||||
--color-primary: ...;
|
||||
--color-bg: ...;
|
||||
|
||||
/* Component tokens (reference color tokens) */
|
||||
--button-bg: var(--color-primary);
|
||||
--card-bg: var(--color-bg);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
| Mistake | Problem | Fix |
|
||||
|---------|---------|-----|
|
||||
| Pure black background | Harsh, looks flat | Use dark gray (#18181b) |
|
||||
| Pure white text | Too much contrast | Use off-white (#fafafa) |
|
||||
| Same saturated colors | Eye strain | Desaturate for dark mode |
|
||||
| Shadows for elevation | Invisible in dark | Use lighter surfaces |
|
||||
| Forgetting images | Can be too bright | Dim images slightly |
|
||||
| One contrast check | Colors interact differently | Check all combinations |
|
||||
| Forgetting focus states | Invisible borders | Ensure visible focus rings |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Minimum Viable Dark Mode
|
||||
|
||||
```css
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-subtle: #f4f4f5;
|
||||
--text: #18181b;
|
||||
--text-muted: #71717a;
|
||||
--border: #e4e4e7;
|
||||
--primary: #2563eb;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #18181b;
|
||||
--bg-subtle: #27272a;
|
||||
--text: #fafafa;
|
||||
--text-muted: #a1a1aa;
|
||||
--border: #3f3f46;
|
||||
--primary: #3b82f6;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,257 @@
|
||||
# UX Heuristic Audit Template
|
||||
|
||||
Structured template for conducting heuristic evaluations. Use this to systematically audit any interface.
|
||||
|
||||
---
|
||||
|
||||
## Audit Information
|
||||
|
||||
**Product/Feature:** ________________
|
||||
**Auditor:** ________________
|
||||
**Date:** ________________
|
||||
**Page/Screen:** ________________
|
||||
**User Context:** ________________ (who is using this, what's their goal?)
|
||||
|
||||
---
|
||||
|
||||
## Severity Scale Reference
|
||||
|
||||
| Rating | Severity | Priority | Description |
|
||||
|--------|----------|----------|-------------|
|
||||
| 0 | Not a problem | — | Cosmetic or disagreement |
|
||||
| 1 | Cosmetic | Low | Minor annoyance |
|
||||
| 2 | Minor | Medium | Delays or frustrates |
|
||||
| 3 | Major | High | Significant task failure |
|
||||
| 4 | Catastrophic | Critical | Prevents task completion |
|
||||
|
||||
---
|
||||
|
||||
## Heuristic Checklist
|
||||
|
||||
### 1. Visibility of System Status
|
||||
|
||||
| Check | Pass? | Issue | Severity |
|
||||
|-------|-------|-------|----------|
|
||||
| Loading states are shown | [ ] | | |
|
||||
| Actions provide immediate feedback | [ ] | | |
|
||||
| Errors are communicated clearly | [ ] | | |
|
||||
| Success is confirmed | [ ] | | |
|
||||
| Progress is visible for long tasks | [ ] | | |
|
||||
|
||||
**Notes:**
|
||||
|
||||
---
|
||||
|
||||
### 2. Match Between System and Real World
|
||||
|
||||
| Check | Pass? | Issue | Severity |
|
||||
|-------|-------|-------|----------|
|
||||
| Uses user's language (not jargon) | [ ] | | |
|
||||
| Icons are recognizable | [ ] | | |
|
||||
| Order follows real-world logic | [ ] | | |
|
||||
| Metaphors are appropriate | [ ] | | |
|
||||
| Terms are consistent with industry | [ ] | | |
|
||||
|
||||
**Notes:**
|
||||
|
||||
---
|
||||
|
||||
### 3. User Control and Freedom
|
||||
|
||||
| Check | Pass? | Issue | Severity |
|
||||
|-------|-------|-------|----------|
|
||||
| Undo is available | [ ] | | |
|
||||
| Cancel/Exit is always accessible | [ ] | | |
|
||||
| Back button works as expected | [ ] | | |
|
||||
| Users can skip optional steps | [ ] | | |
|
||||
| Modals have clear close options | [ ] | | |
|
||||
|
||||
**Notes:**
|
||||
|
||||
---
|
||||
|
||||
### 4. Consistency and Standards
|
||||
|
||||
| Check | Pass? | Issue | Severity |
|
||||
|-------|-------|-------|----------|
|
||||
| Same terms for same concepts | [ ] | | |
|
||||
| Buttons/links styled consistently | [ ] | | |
|
||||
| Follows platform conventions | [ ] | | |
|
||||
| Layout is predictable | [ ] | | |
|
||||
| Same actions work the same way | [ ] | | |
|
||||
|
||||
**Notes:**
|
||||
|
||||
---
|
||||
|
||||
### 5. Error Prevention
|
||||
|
||||
| Check | Pass? | Issue | Severity |
|
||||
|-------|-------|-------|----------|
|
||||
| Destructive actions require confirmation | [ ] | | |
|
||||
| Inline validation prevents bad submissions | [ ] | | |
|
||||
| Constrained inputs prevent errors | [ ] | | |
|
||||
| Defaults are sensible | [ ] | | |
|
||||
| "Unsaved changes" warning exists | [ ] | | |
|
||||
|
||||
**Notes:**
|
||||
|
||||
---
|
||||
|
||||
### 6. Recognition Rather Than Recall
|
||||
|
||||
| Check | Pass? | Issue | Severity |
|
||||
|-------|-------|-------|----------|
|
||||
| Options are visible (not hidden) | [ ] | | |
|
||||
| Recent items/history is accessible | [ ] | | |
|
||||
| Icons have labels | [ ] | | |
|
||||
| Instructions are in context | [ ] | | |
|
||||
| Previous input is remembered | [ ] | | |
|
||||
|
||||
**Notes:**
|
||||
|
||||
---
|
||||
|
||||
### 7. Flexibility and Efficiency
|
||||
|
||||
| Check | Pass? | Issue | Severity |
|
||||
|-------|-------|-------|----------|
|
||||
| Keyboard shortcuts for power users | [ ] | | |
|
||||
| Bulk actions available | [ ] | | |
|
||||
| Customization options exist | [ ] | | |
|
||||
| Frequent tasks are quick | [ ] | | |
|
||||
| Advanced features don't clutter basics | [ ] | | |
|
||||
|
||||
**Notes:**
|
||||
|
||||
---
|
||||
|
||||
### 8. Aesthetic and Minimalist Design
|
||||
|
||||
| Check | Pass? | Issue | Severity |
|
||||
|-------|-------|-------|----------|
|
||||
| Only essential info is shown | [ ] | | |
|
||||
| Visual hierarchy is clear | [ ] | | |
|
||||
| Whitespace is used effectively | [ ] | | |
|
||||
| No unnecessary decoration | [ ] | | |
|
||||
| Primary action is obvious | [ ] | | |
|
||||
|
||||
**Notes:**
|
||||
|
||||
---
|
||||
|
||||
### 9. Error Recovery
|
||||
|
||||
| Check | Pass? | Issue | Severity |
|
||||
|-------|-------|-------|----------|
|
||||
| Error messages are in plain language | [ ] | | |
|
||||
| Errors explain what went wrong | [ ] | | |
|
||||
| Errors suggest how to fix | [ ] | | |
|
||||
| User input is preserved on error | [ ] | | |
|
||||
| Error location is highlighted | [ ] | | |
|
||||
|
||||
**Notes:**
|
||||
|
||||
---
|
||||
|
||||
### 10. Help and Documentation
|
||||
|
||||
| Check | Pass? | Issue | Severity |
|
||||
|-------|-------|-------|----------|
|
||||
| Help is searchable | [ ] | | |
|
||||
| Contextual help is available | [ ] | | |
|
||||
| Documentation is task-focused | [ ] | | |
|
||||
| Tooltips explain non-obvious features | [ ] | | |
|
||||
| Support is accessible | [ ] | | |
|
||||
|
||||
**Notes:**
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
### Issue Count by Severity
|
||||
|
||||
| Severity | Count |
|
||||
|----------|-------|
|
||||
| 4 (Catastrophic) | |
|
||||
| 3 (Major) | |
|
||||
| 2 (Minor) | |
|
||||
| 1 (Cosmetic) | |
|
||||
|
||||
### Top Issues (Severity 3-4)
|
||||
|
||||
| # | Issue | Heuristic | Severity | Recommendation |
|
||||
|---|-------|-----------|----------|----------------|
|
||||
| 1 | | | | |
|
||||
| 2 | | | | |
|
||||
| 3 | | | | |
|
||||
| 4 | | | | |
|
||||
| 5 | | | | |
|
||||
|
||||
### Quick Wins (Low effort, high impact)
|
||||
|
||||
| Issue | Fix | Effort |
|
||||
|-------|-----|--------|
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
|
||||
---
|
||||
|
||||
## Task-Based Audit (Optional)
|
||||
|
||||
Test specific user tasks and note where issues occur.
|
||||
|
||||
### Task 1: ________________
|
||||
|
||||
| Step | Expected | Actual | Issue | Severity |
|
||||
|------|----------|--------|-------|----------|
|
||||
| 1 | | | | |
|
||||
| 2 | | | | |
|
||||
| 3 | | | | |
|
||||
|
||||
### Task 2: ________________
|
||||
|
||||
| Step | Expected | Actual | Issue | Severity |
|
||||
|------|----------|--------|-------|----------|
|
||||
| 1 | | | | |
|
||||
| 2 | | | | |
|
||||
| 3 | | | | |
|
||||
|
||||
---
|
||||
|
||||
## Navigation/Trunk Test
|
||||
|
||||
Can users answer these questions immediately?
|
||||
|
||||
| Question | Can Answer? | Notes |
|
||||
|----------|-------------|-------|
|
||||
| What site is this? | [ ] Yes [ ] No | |
|
||||
| What page am I on? | [ ] Yes [ ] No | |
|
||||
| What are the major sections? | [ ] Yes [ ] No | |
|
||||
| What are my options here? | [ ] Yes [ ] No | |
|
||||
| Where am I in the site structure? | [ ] Yes [ ] No | |
|
||||
| How can I search? | [ ] Yes [ ] No | |
|
||||
|
||||
---
|
||||
|
||||
## Recommendations Summary
|
||||
|
||||
### Immediate (Severity 4)
|
||||
1.
|
||||
2.
|
||||
|
||||
### Short-term (Severity 3)
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
### Medium-term (Severity 2)
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
### Low Priority (Severity 1)
|
||||
1.
|
||||
2.
|
||||
@@ -0,0 +1,346 @@
|
||||
# Cultural UX Considerations
|
||||
|
||||
Designing for global audiences: RTL languages, color meanings, form conventions, and localization.
|
||||
|
||||
## Right-to-Left (RTL) Languages
|
||||
|
||||
### Languages That Use RTL
|
||||
|
||||
- Arabic
|
||||
- Hebrew
|
||||
- Persian (Farsi)
|
||||
- Urdu
|
||||
|
||||
### What to Mirror
|
||||
|
||||
| Element | LTR | RTL |
|
||||
|---------|-----|-----|
|
||||
| Text alignment | Left | Right |
|
||||
| Navigation | Left side | Right side |
|
||||
| Progress indicators | Left to right | Right to left |
|
||||
| Icons with direction | → | ← |
|
||||
| Checkmarks | ✓ on right | ✓ on left |
|
||||
| Back buttons | ← | → |
|
||||
| Carousels/sliders | Swipe left for next | Swipe right for next |
|
||||
|
||||
### What NOT to Mirror
|
||||
|
||||
| Element | Reason |
|
||||
|---------|--------|
|
||||
| Numbers | Always LTR (123, not ٣٢١) |
|
||||
| Phone numbers | Universal format |
|
||||
| Clocks | Universal format |
|
||||
| Video player controls | Universal convention |
|
||||
| Brand logos | Design integrity |
|
||||
| Math equations | Universal notation |
|
||||
|
||||
### CSS for RTL
|
||||
|
||||
```css
|
||||
/* Modern approach using logical properties */
|
||||
.element {
|
||||
/* Instead of margin-left: 16px */
|
||||
margin-inline-start: 16px;
|
||||
|
||||
/* Instead of padding-right: 8px */
|
||||
padding-inline-end: 8px;
|
||||
|
||||
/* Instead of text-align: left */
|
||||
text-align: start;
|
||||
|
||||
/* Instead of float: left */
|
||||
float: inline-start;
|
||||
}
|
||||
|
||||
/* Or use dir attribute */
|
||||
[dir="rtl"] .element {
|
||||
/* RTL-specific overrides */
|
||||
}
|
||||
```
|
||||
|
||||
### RTL Icons
|
||||
|
||||
Icons with inherent direction need mirroring:
|
||||
|
||||
| Icon | Mirror? | Reason |
|
||||
|------|---------|--------|
|
||||
| Back arrow | Yes | Directional navigation |
|
||||
| Forward arrow | Yes | Directional navigation |
|
||||
| Reply icon | Yes | Shows direction of response |
|
||||
| Search icon | No | Magnifying glass is universal |
|
||||
| Home icon | No | House has no direction |
|
||||
| Settings gear | No | Symmetric |
|
||||
| Play button | No | Universal media convention |
|
||||
|
||||
---
|
||||
|
||||
## Color Meanings Across Cultures
|
||||
|
||||
### Red
|
||||
|
||||
| Culture | Meaning |
|
||||
|---------|---------|
|
||||
| Western | Danger, stop, error, love |
|
||||
| China | Good luck, prosperity, happiness |
|
||||
| India | Purity, fertility |
|
||||
| South Africa | Mourning |
|
||||
| Japan | Life, anger |
|
||||
|
||||
**Design implication:** Don't use red for error states in apps targeting Chinese markets without context.
|
||||
|
||||
### White
|
||||
|
||||
| Culture | Meaning |
|
||||
|---------|---------|
|
||||
| Western | Purity, cleanliness, peace |
|
||||
| China/Japan | Death, mourning |
|
||||
| India | Unhappiness, mourning |
|
||||
|
||||
**Design implication:** White space may have different connotations; "clean" design may feel empty or cold.
|
||||
|
||||
### Green
|
||||
|
||||
| Culture | Meaning |
|
||||
|---------|---------|
|
||||
| Western | Go, success, nature, money |
|
||||
| Islamic | Sacred, paradise |
|
||||
| China | Infidelity (green hat = cuckold) |
|
||||
| Ireland | National identity |
|
||||
|
||||
**Design implication:** Green success states work globally, but be careful with green accessories or hats in Chinese contexts.
|
||||
|
||||
### Yellow
|
||||
|
||||
| Culture | Meaning |
|
||||
|---------|---------|
|
||||
| Western | Caution, happiness |
|
||||
| Japan | Courage, royalty |
|
||||
| Egypt | Mourning |
|
||||
| Latin America | Death, mourning |
|
||||
|
||||
### Blue
|
||||
|
||||
| Culture | Meaning |
|
||||
|---------|---------|
|
||||
| Most cultures | Trust, calm, professionalism |
|
||||
| Iran | Mourning, spirituality |
|
||||
|
||||
**Design implication:** Blue is relatively safe globally; commonly used for links and primary actions.
|
||||
|
||||
### General Guidelines
|
||||
|
||||
1. **Test with local users** - Color perception varies
|
||||
2. **Don't rely on color alone** - Add icons and text
|
||||
3. **Provide customization** - Let users choose colors where possible
|
||||
4. **Research target markets** - Specific meanings in your target regions
|
||||
|
||||
---
|
||||
|
||||
## Form Conventions
|
||||
|
||||
### Name Fields
|
||||
|
||||
| Culture | Name Structure |
|
||||
|---------|----------------|
|
||||
| Western | First + Last (given + family) |
|
||||
| China/Japan/Korea | Family + Given |
|
||||
| Iceland | Given + Patronymic |
|
||||
| Spanish | Given + Father's surname + Mother's surname |
|
||||
| Arabic | Given + Father's + Grandfather's + Family |
|
||||
|
||||
**Best practices:**
|
||||
- Use single "Full Name" field when possible
|
||||
- If splitting, use "Given Name" and "Family Name" (not First/Last)
|
||||
- Don't assume first/last order
|
||||
- Allow long names (>50 characters)
|
||||
|
||||
### Address Fields
|
||||
|
||||
| Region | Considerations |
|
||||
|--------|----------------|
|
||||
| US | State, ZIP code (5 or 9 digits) |
|
||||
| UK | County optional, postcode format varies |
|
||||
| Japan | Prefecture, address reads large→small |
|
||||
| Brazil | CEP codes |
|
||||
| Countries without postal codes | 50+ countries don't use them |
|
||||
|
||||
**Best practices:**
|
||||
- Don't require postal code universally
|
||||
- Use country-appropriate field labels
|
||||
- Allow flexible formats
|
||||
- Consider address autocomplete services
|
||||
|
||||
### Phone Numbers
|
||||
|
||||
| Consideration | Approach |
|
||||
|---------------|----------|
|
||||
| Country codes | Allow input or select separately |
|
||||
| Length | Varies widely (7-15 digits) |
|
||||
| Format | Don't enforce specific format |
|
||||
| Mobile vs landline | Labels may not translate |
|
||||
|
||||
**Best practices:**
|
||||
- Accept multiple formats
|
||||
- Store in E.164 format internally (+1234567890)
|
||||
- Display in local format
|
||||
- Don't validate too strictly
|
||||
|
||||
### Dates
|
||||
|
||||
| Region | Format |
|
||||
|--------|--------|
|
||||
| US | MM/DD/YYYY |
|
||||
| Most of world | DD/MM/YYYY |
|
||||
| Japan, China, Korea | YYYY/MM/DD |
|
||||
| ISO standard | YYYY-MM-DD |
|
||||
|
||||
**Best practices:**
|
||||
- Use date pickers instead of text input
|
||||
- Show month names (not numbers) to avoid confusion
|
||||
- Store in ISO format (YYYY-MM-DD)
|
||||
- Display in user's locale
|
||||
|
||||
### Currency
|
||||
|
||||
| Consideration | Examples |
|
||||
|---------------|----------|
|
||||
| Symbol position | $100 vs 100€ vs 100 kr |
|
||||
| Decimal separator | $1,234.56 vs €1.234,56 |
|
||||
| Thousands separator | 1,000 vs 1.000 vs 1 000 |
|
||||
| Currency names | Dollar, Yuan, Rupee |
|
||||
|
||||
**Best practices:**
|
||||
- Format according to user locale
|
||||
- Always show currency symbol/code
|
||||
- Be clear about which currency
|
||||
- Handle conversion if multi-currency
|
||||
|
||||
---
|
||||
|
||||
## Text Considerations
|
||||
|
||||
### Text Expansion
|
||||
|
||||
Translated text is often longer than English:
|
||||
|
||||
| Language | Expansion Factor |
|
||||
|----------|------------------|
|
||||
| German | 1.3x |
|
||||
| French | 1.2x |
|
||||
| Russian | 1.2x |
|
||||
| Spanish | 1.2x |
|
||||
| Chinese | 0.8x |
|
||||
| Japanese | 0.9x |
|
||||
|
||||
**Design implications:**
|
||||
- Don't design to exact English text length
|
||||
- Allow buttons and labels to expand
|
||||
- Test layouts with longest expected translations
|
||||
- Use flexible grid/flexbox layouts
|
||||
|
||||
### Text in Images
|
||||
|
||||
Avoid text in images because:
|
||||
- Can't be translated easily
|
||||
- Not accessible to screen readers
|
||||
- Doesn't scale with user preferences
|
||||
- Complicates localization workflow
|
||||
|
||||
**Alternative:** Use CSS text over images.
|
||||
|
||||
### Numbers and Units
|
||||
|
||||
| Element | Consideration |
|
||||
|---------|---------------|
|
||||
| Measurement | Metric (most world) vs Imperial (US) |
|
||||
| Paper size | A4 (most world) vs Letter (US, Canada) |
|
||||
| Temperature | Celsius vs Fahrenheit |
|
||||
| Time | 24-hour vs 12-hour (AM/PM) |
|
||||
|
||||
---
|
||||
|
||||
## Icons and Imagery
|
||||
|
||||
### Potentially Problematic Icons
|
||||
|
||||
| Icon | Issue | Alternative |
|
||||
|------|-------|-------------|
|
||||
| Mailbox | US-specific design | Envelope |
|
||||
| Check mark | Means "wrong" in some cultures | Consider context |
|
||||
| Thumbs up | Offensive in some cultures | Hearts, stars |
|
||||
| Hand gestures | Vary widely in meaning | Avoid gestures |
|
||||
| Animals | Religious/cultural sensitivities | Research target market |
|
||||
| Religious symbols | May exclude or offend | Use neutral symbols |
|
||||
|
||||
### Photography
|
||||
|
||||
| Consideration | Approach |
|
||||
|---------------|----------|
|
||||
| Diversity | Represent target audience |
|
||||
| Gestures | Avoid culture-specific gestures |
|
||||
| Clothing | Consider cultural norms |
|
||||
| Settings | Use locally relevant contexts |
|
||||
| Food | Be aware of dietary restrictions |
|
||||
|
||||
---
|
||||
|
||||
## Localization Best Practices
|
||||
|
||||
### Technical
|
||||
|
||||
```javascript
|
||||
// Use internationalization libraries
|
||||
const formatter = new Intl.DateTimeFormat('de-DE');
|
||||
const date = formatter.format(new Date());
|
||||
|
||||
const currencyFormatter = new Intl.NumberFormat('ja-JP', {
|
||||
style: 'currency',
|
||||
currency: 'JPY'
|
||||
});
|
||||
const price = currencyFormatter.format(1000);
|
||||
```
|
||||
|
||||
### Content
|
||||
|
||||
- Use simple, clear language (easier to translate)
|
||||
- Avoid idioms and colloquialisms
|
||||
- Don't hardcode strings (use translation keys)
|
||||
- Provide context for translators
|
||||
- Test with pseudo-localization during development
|
||||
|
||||
### Design
|
||||
|
||||
- Design for flexibility (expanding text)
|
||||
- Use icons with text (not alone)
|
||||
- Test layouts in RTL and longest languages
|
||||
- Consider reading order in complex layouts
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Checklist
|
||||
|
||||
Before launching internationally:
|
||||
|
||||
**Layout:**
|
||||
- [ ] Supports RTL if targeting Arabic, Hebrew, Persian
|
||||
- [ ] Text can expand 30% without breaking
|
||||
- [ ] No text in images
|
||||
- [ ] Icons are culturally neutral
|
||||
|
||||
**Forms:**
|
||||
- [ ] Name fields are flexible
|
||||
- [ ] Addresses work without postal codes
|
||||
- [ ] Phone numbers accept various formats
|
||||
- [ ] Dates use pickers or clear formats
|
||||
|
||||
**Content:**
|
||||
- [ ] Colors don't carry unintended meaning
|
||||
- [ ] Images represent target audience
|
||||
- [ ] No culture-specific idioms
|
||||
- [ ] Units match target region
|
||||
|
||||
**Technical:**
|
||||
- [ ] Dates, numbers, currency use locale formatting
|
||||
- [ ] All strings externalized for translation
|
||||
- [ ] Character encoding supports target languages
|
||||
- [ ] Font supports required character sets
|
||||
@@ -0,0 +1,362 @@
|
||||
# Dark Patterns in UX Design
|
||||
|
||||
Understanding manipulative design practices to recognize and avoid them. Ethical alternatives that achieve business goals without deceiving users.
|
||||
|
||||
## What Are Dark Patterns?
|
||||
|
||||
Dark patterns are user interface designs that trick users into doing things they didn't intend. They exploit cognitive biases and psychological vulnerabilities for business benefit at user expense.
|
||||
|
||||
**Key distinction:**
|
||||
- Persuasion = helping users make decisions aligned with their goals
|
||||
- Dark patterns = tricking users into decisions against their interests
|
||||
|
||||
---
|
||||
|
||||
## Categories of Dark Patterns
|
||||
|
||||
### 1. Forced Continuity
|
||||
|
||||
**Definition:** Making it easy to sign up for a free trial but difficult to cancel.
|
||||
|
||||
**Examples:**
|
||||
- Require phone call to cancel (but signup was online)
|
||||
- Bury cancellation in settings maze
|
||||
- Show "Are you sure?" modals repeatedly
|
||||
- Require cancellation 30 days before renewal
|
||||
|
||||
**Why it's harmful:**
|
||||
- Users pay for services they don't want
|
||||
- Exploits inertia and forgetfulness
|
||||
- Damages trust when discovered
|
||||
|
||||
**Ethical alternative:**
|
||||
- Cancel button as easy to find as signup
|
||||
- Clear cancellation confirmation (not guilt trips)
|
||||
- Email reminder before renewal charge
|
||||
- Allow pause instead of forcing cancel
|
||||
|
||||
### 2. Roach Motel
|
||||
|
||||
**Definition:** Easy to get into a situation, hard to get out.
|
||||
|
||||
**Examples:**
|
||||
- Account creation takes 1 minute, deletion takes 30 days and support tickets
|
||||
- Subscribing to emails requires one click, unsubscribing requires login + multiple confirmations
|
||||
- Joining is free, but exported data costs money
|
||||
|
||||
**Why it's harmful:**
|
||||
- Traps users against their will
|
||||
- Violates user autonomy
|
||||
- Often illegal under GDPR and similar regulations
|
||||
|
||||
**Ethical alternative:**
|
||||
- Symmetric design: if X is easy, reversing X should be easy
|
||||
- Account deletion should be self-service
|
||||
- Data export should be free and complete
|
||||
- Unsubscribe = one click
|
||||
|
||||
### 3. Privacy Zuckering
|
||||
|
||||
**Definition:** Tricking users into sharing more information than intended.
|
||||
|
||||
**Examples:**
|
||||
- Default settings share everything publicly
|
||||
- "Connect with friends" imports entire contact list
|
||||
- Profile completion gamification encourages oversharing
|
||||
- Confusing privacy controls that require expertise
|
||||
|
||||
**Why it's harmful:**
|
||||
- Users lose control of personal information
|
||||
- Can lead to real-world harm (stalking, discrimination)
|
||||
- Exploits the complexity of privacy settings
|
||||
|
||||
**Ethical alternative:**
|
||||
- Privacy-respecting defaults (share nothing by default)
|
||||
- Clear, plain-language privacy explanations
|
||||
- Granular, understandable controls
|
||||
- Regular privacy checkups that surface settings
|
||||
|
||||
### 4. Bait and Switch
|
||||
|
||||
**Definition:** User sets out to do one thing, but something different happens.
|
||||
|
||||
**Examples:**
|
||||
- "X" button that triggers action instead of closing
|
||||
- "Download" button that's actually an ad
|
||||
- "Free trial" that immediately charges
|
||||
- Changing terms after user commits to purchase
|
||||
|
||||
**Why it's harmful:**
|
||||
- Directly deceives users about consequences
|
||||
- Violates fundamental expectations
|
||||
- Often results in unwanted charges or actions
|
||||
|
||||
**Ethical alternative:**
|
||||
- Buttons do exactly what they say
|
||||
- Clear labeling distinguishes ads from content
|
||||
- Free trials are genuinely free until stated conversion point
|
||||
- Terms are locked at time of agreement
|
||||
|
||||
### 5. Confirmshaming
|
||||
|
||||
**Definition:** Using guilt or shame to manipulate users into opting in.
|
||||
|
||||
**Examples:**
|
||||
- "No thanks, I don't want to save money"
|
||||
- "I'll stay ignorant" (for newsletter)
|
||||
- "I don't care about my health"
|
||||
- Imagery showing sad faces for decline option
|
||||
|
||||
**Why it's harmful:**
|
||||
- Manipulates emotions to override rational decision
|
||||
- Disrespects user autonomy
|
||||
- Creates negative brand association
|
||||
|
||||
**Ethical alternative:**
|
||||
- Neutral decline options: "No thanks" or "Maybe later"
|
||||
- Equal visual weight for both choices
|
||||
- Respect the "no" without comment
|
||||
- Focus on value proposition, not guilt
|
||||
|
||||
### 6. Hidden Costs
|
||||
|
||||
**Definition:** Prices or fees revealed only at final checkout.
|
||||
|
||||
**Examples:**
|
||||
- Service fees added at last step
|
||||
- Required "convenience fees"
|
||||
- Shipping costs revealed after entering payment info
|
||||
- "Processing fees" on top of advertised price
|
||||
|
||||
**Why it's harmful:**
|
||||
- Users commit time/effort before learning true cost
|
||||
- Exploits sunk cost fallacy
|
||||
- Illegal in many jurisdictions (price must be clear)
|
||||
|
||||
**Ethical alternative:**
|
||||
- Show total cost including all fees upfront
|
||||
- If fees depend on choices, show estimates early
|
||||
- Price transparency builds trust
|
||||
- All-in pricing where possible
|
||||
|
||||
### 7. Misdirection
|
||||
|
||||
**Definition:** Design draws attention away from important information.
|
||||
|
||||
**Examples:**
|
||||
- Terms and conditions in tiny gray text
|
||||
- "Yes" button prominent, "No" button hidden
|
||||
- Pre-selected add-ons that require unchecking
|
||||
- Important disclaimers below the fold
|
||||
|
||||
**Why it's harmful:**
|
||||
- Prevents informed decision-making
|
||||
- Hides information users would want to know
|
||||
- Exploits visual hierarchy against users
|
||||
|
||||
**Ethical alternative:**
|
||||
- Important information is visually prominent
|
||||
- Both options equally accessible
|
||||
- Nothing pre-selected that costs money
|
||||
- Disclaimers at point of relevance, not hidden
|
||||
|
||||
### 8. Trick Questions
|
||||
|
||||
**Definition:** Confusing wording that leads to unintended choices.
|
||||
|
||||
**Examples:**
|
||||
- "Uncheck to not receive no emails" (double negative)
|
||||
- Checkboxes that mean opposite things mixed together
|
||||
- "Continue" meaning "I agree" without stating so
|
||||
- Questions worded to confuse opt-in vs opt-out
|
||||
|
||||
**Why it's harmful:**
|
||||
- Deliberately confuses users
|
||||
- Results in choices user didn't mean to make
|
||||
- Exploits cognitive load
|
||||
|
||||
**Ethical alternative:**
|
||||
- Clear, simple language
|
||||
- Consistent meaning (check = yes, uncheck = no)
|
||||
- Explicit confirmation language
|
||||
- User testing to catch confusing wording
|
||||
|
||||
### 9. Sneak into Basket
|
||||
|
||||
**Definition:** Items added to cart without user action.
|
||||
|
||||
**Examples:**
|
||||
- Insurance pre-selected during checkout
|
||||
- "Protection plan" added by default
|
||||
- Donation to charity checked by default
|
||||
- Accessories added when buying main product
|
||||
|
||||
**Why it's harmful:**
|
||||
- Users pay for things they didn't choose
|
||||
- Exploits inattention during checkout
|
||||
- Often hidden in long checkout flows
|
||||
|
||||
**Ethical alternative:**
|
||||
- Nothing added without explicit user action
|
||||
- Optional items clearly offered (not pre-selected)
|
||||
- Cart contents always visible and editable
|
||||
- Confirmation of what's being purchased
|
||||
|
||||
### 10. Urgency & Scarcity (False)
|
||||
|
||||
**Definition:** Creating fake urgency or scarcity to pressure decisions.
|
||||
|
||||
**Examples:**
|
||||
- "Only 2 left!" (restocked hourly)
|
||||
- "This deal expires in 10:00" (resets on refresh)
|
||||
- "15 people viewing this" (fabricated)
|
||||
- "Prices increase tomorrow" (they don't)
|
||||
|
||||
**Why it's harmful:**
|
||||
- Pressures users into hasty decisions
|
||||
- Based on lies (not real scarcity)
|
||||
- Prevents price comparison and consideration
|
||||
- Particularly harmful for high-stakes purchases
|
||||
|
||||
**Ethical alternative:**
|
||||
- Only show real inventory counts
|
||||
- Honest sale end dates
|
||||
- If scarcity is real, explain why
|
||||
- Give users time to decide
|
||||
|
||||
---
|
||||
|
||||
## Regulatory Context
|
||||
|
||||
### GDPR (Europe)
|
||||
|
||||
Dark patterns affecting consent are illegal:
|
||||
- Consent must be freely given
|
||||
- Rejecting must be as easy as accepting
|
||||
- Pre-ticked boxes invalid for consent
|
||||
- Bundled consent (all-or-nothing) invalid
|
||||
|
||||
### FTC (United States)
|
||||
|
||||
The FTC has taken action against:
|
||||
- Hidden subscription fees
|
||||
- Difficult cancellation processes
|
||||
- Misleading "free trial" offers
|
||||
- Fake urgency and scarcity
|
||||
|
||||
### California Privacy Rights Act (CPRA)
|
||||
|
||||
Specifically prohibits:
|
||||
- Dark patterns in opt-out processes
|
||||
- Requires symmetry in design
|
||||
- Consent obtained through dark patterns is invalid
|
||||
|
||||
---
|
||||
|
||||
## How to Audit for Dark Patterns
|
||||
|
||||
### Checklist
|
||||
|
||||
**Signup/Subscription:**
|
||||
- [ ] Can users cancel as easily as they signed up?
|
||||
- [ ] Are renewal terms clear at signup?
|
||||
- [ ] Is the "free" trial genuinely free?
|
||||
|
||||
**Checkout:**
|
||||
- [ ] Is the total price clear before final step?
|
||||
- [ ] Are all added items explicitly chosen by user?
|
||||
- [ ] Are opt-outs as prominent as opt-ins?
|
||||
|
||||
**Data/Privacy:**
|
||||
- [ ] Are privacy settings understandable?
|
||||
- [ ] Are defaults privacy-respecting?
|
||||
- [ ] Can users export/delete their data easily?
|
||||
|
||||
**General:**
|
||||
- [ ] Does every button do what it says?
|
||||
- [ ] Is important information visually prominent?
|
||||
- [ ] Are decline options neutral (no shaming)?
|
||||
- [ ] Is urgency/scarcity real?
|
||||
|
||||
### The Mirror Test
|
||||
|
||||
Ask: "Would I feel comfortable if a journalist wrote about how this works?"
|
||||
|
||||
If the answer is no, it's probably a dark pattern.
|
||||
|
||||
---
|
||||
|
||||
## Ethical Alternatives That Work
|
||||
|
||||
### Instead of Forced Continuity
|
||||
|
||||
**Business goal:** Retain subscribers
|
||||
|
||||
**Ethical approach:**
|
||||
- Make the product so good they don't want to cancel
|
||||
- Offer pause option instead of cancel
|
||||
- Win-back campaigns for churned users
|
||||
- Ask why they're leaving and address it
|
||||
|
||||
### Instead of Hidden Costs
|
||||
|
||||
**Business goal:** Competitive-looking prices
|
||||
|
||||
**Ethical approach:**
|
||||
- All-in pricing (include fees in advertised price)
|
||||
- Compete on value, not deceptive pricing
|
||||
- Explain what fees cover (transparency builds trust)
|
||||
- Offer fee-free options (digital delivery, etc.)
|
||||
|
||||
### Instead of Confirmshaming
|
||||
|
||||
**Business goal:** Higher opt-in rates
|
||||
|
||||
**Ethical approach:**
|
||||
- Stronger value proposition
|
||||
- Social proof (join 100k subscribers)
|
||||
- Clear benefit statement
|
||||
- Respect "no" and try again later
|
||||
|
||||
### Instead of False Urgency
|
||||
|
||||
**Business goal:** Faster purchase decisions
|
||||
|
||||
**Ethical approach:**
|
||||
- Genuine limited-time offers (and honor them)
|
||||
- Waitlists for genuinely scarce items
|
||||
- Early access for committed customers
|
||||
- Value-based urgency (limited capacity, real deadlines)
|
||||
|
||||
---
|
||||
|
||||
## The Business Case Against Dark Patterns
|
||||
|
||||
### Short-term vs Long-term
|
||||
|
||||
| Metric | Dark Pattern Impact | Ethical Design Impact |
|
||||
|--------|---------------------|----------------------|
|
||||
| Initial conversion | ↑ Higher | Slightly lower |
|
||||
| Customer trust | ↓ Lower | ↑ Higher |
|
||||
| Churn rate | ↑ Higher | ↓ Lower |
|
||||
| Customer lifetime value | ↓ Lower | ↑ Higher |
|
||||
| Word of mouth | Negative | Positive |
|
||||
| Regulatory risk | High | Low |
|
||||
|
||||
### Real Costs of Dark Patterns
|
||||
|
||||
1. **Support costs** - Dealing with angry customers
|
||||
2. **Chargeback rates** - Users disputing unwanted charges
|
||||
3. **Reputation damage** - Social media exposure
|
||||
4. **Legal fees** - Defending against lawsuits
|
||||
5. **Regulatory fines** - Increasing enforcement
|
||||
6. **Employee morale** - Good people don't want to deceive users
|
||||
|
||||
### Companies That Changed
|
||||
|
||||
Several major companies have eliminated dark patterns after backlash:
|
||||
- LinkedIn simplified privacy controls after criticism
|
||||
- Amazon made "Subscribe & Save" more transparent
|
||||
- Apple added App Store subscription management
|
||||
|
||||
The pattern: Short-term thinking creates dark patterns; long-term thinking removes them.
|
||||
@@ -0,0 +1,296 @@
|
||||
# Resolving Heuristic Conflicts
|
||||
|
||||
When usability principles contradict each other—frameworks for making trade-off decisions.
|
||||
|
||||
## The Nature of Conflicts
|
||||
|
||||
Usability heuristics are guidelines, not laws. In real design, they often pull in opposite directions. Good UX design requires recognizing these tensions and making thoughtful trade-offs.
|
||||
|
||||
**Common tension patterns:**
|
||||
- Simplicity vs. Power
|
||||
- Consistency vs. Optimal for context
|
||||
- Efficiency vs. Error prevention
|
||||
- User control vs. Guidance
|
||||
- Discoverability vs. Clean interface
|
||||
|
||||
---
|
||||
|
||||
## Major Heuristic Conflicts
|
||||
|
||||
### 1. Simplicity vs. Flexibility
|
||||
|
||||
**The tension:**
|
||||
- "Keep it simple" (Krug, Nielsen)
|
||||
- "Support user control and freedom" (Nielsen #3)
|
||||
- "Flexibility and efficiency of use" (Nielsen #7)
|
||||
|
||||
**Example:** Photo editing app
|
||||
- Simple: Few options, quick editing
|
||||
- Flexible: Many controls, professional results
|
||||
|
||||
**Resolution framework:**
|
||||
|
||||
| Factor | Lean Simple | Lean Flexible |
|
||||
|--------|-------------|---------------|
|
||||
| User expertise | Novice | Expert |
|
||||
| Task frequency | Occasional | Daily |
|
||||
| Consequences of error | Low | High |
|
||||
| Time pressure | High | Low |
|
||||
|
||||
**Design patterns:**
|
||||
- Progressive disclosure (simple default, power underneath)
|
||||
- Personas-based modes (Basic/Advanced)
|
||||
- Contextual features (show when relevant)
|
||||
|
||||
### 2. Consistency vs. Context Optimization
|
||||
|
||||
**The tension:**
|
||||
- "Consistency and standards" (Nielsen #4)
|
||||
- "Match between system and real world" (Nielsen #2)
|
||||
|
||||
**Example:** Destructive actions
|
||||
- Consistent: Red "Delete" button everywhere
|
||||
- Context-optimized: Prominent delete for items that should be deleted, hidden for critical data
|
||||
|
||||
**Resolution framework:**
|
||||
|
||||
| Factor | Lean Consistent | Lean Contextual |
|
||||
|--------|-----------------|-----------------|
|
||||
| User base diversity | High | Low |
|
||||
| Learning curve concern | Yes | No |
|
||||
| Action frequency | Common | Rare |
|
||||
| Mental model strength | Established | Forming |
|
||||
|
||||
**Design patterns:**
|
||||
- Consistent patterns, contextual prominence
|
||||
- Same actions, different emphasis
|
||||
- Gradual introduction of contextual variations
|
||||
|
||||
### 3. Efficiency vs. Error Prevention
|
||||
|
||||
**The tension:**
|
||||
- "Error prevention" (Nielsen #5)
|
||||
- "Flexibility and efficiency of use" (Nielsen #7)
|
||||
|
||||
**Example:** Checkout flow
|
||||
- Error prevention: Confirmation at every step
|
||||
- Efficiency: One-click purchase
|
||||
|
||||
**Resolution framework:**
|
||||
|
||||
| Factor | Lean Error Prevention | Lean Efficiency |
|
||||
|--------|----------------------|-----------------|
|
||||
| Reversibility | Irreversible | Easily undone |
|
||||
| Cost of error | High (money, data) | Low |
|
||||
| User familiarity | New users | Power users |
|
||||
| Frequency | Rare | Very frequent |
|
||||
|
||||
**Design patterns:**
|
||||
- Undo instead of confirm (efficient + safe)
|
||||
- Confidence-based friction (add steps for risky actions)
|
||||
- User-controlled safety level
|
||||
|
||||
### 4. Discoverability vs. Clean Interface
|
||||
|
||||
**The tension:**
|
||||
- "Recognition rather than recall" (Nielsen #6)
|
||||
- "Aesthetic and minimalist design" (Nielsen #8)
|
||||
|
||||
**Example:** Feature-rich application
|
||||
- Discoverable: Show all options visibly
|
||||
- Clean: Hide features until needed
|
||||
|
||||
**Resolution framework:**
|
||||
|
||||
| Factor | Lean Discoverable | Lean Clean |
|
||||
|--------|-------------------|------------|
|
||||
| Feature frequency | Core features | Edge cases |
|
||||
| User expertise | Beginner | Expert |
|
||||
| Task complexity | Simple | Complex |
|
||||
| Screen real estate | Generous | Limited |
|
||||
|
||||
**Design patterns:**
|
||||
- Primary actions visible, secondary in menus
|
||||
- Progressive disclosure
|
||||
- Contextual menus
|
||||
- Search/command palettes for power users
|
||||
|
||||
### 5. Guidance vs. User Control
|
||||
|
||||
**The tension:**
|
||||
- Help users avoid errors
|
||||
- Respect user autonomy
|
||||
|
||||
**Example:** Form validation
|
||||
- Guidance: Prevent submission until valid
|
||||
- Control: Let users submit and see what happens
|
||||
|
||||
**Resolution framework:**
|
||||
|
||||
| Factor | Lean Guidance | Lean Control |
|
||||
|--------|---------------|--------------|
|
||||
| User expertise | Novice | Expert |
|
||||
| Error recovery | Difficult | Easy |
|
||||
| System tolerance | Low (strict rules) | High (flexible) |
|
||||
| User frustration with restrictions | Low | High |
|
||||
|
||||
**Design patterns:**
|
||||
- Inline guidance (not blocking)
|
||||
- Warnings vs. blockers
|
||||
- "Are you sure?" rather than "You can't"
|
||||
|
||||
---
|
||||
|
||||
## Resolution Framework
|
||||
|
||||
### Step 1: Identify the Conflict
|
||||
|
||||
Name the specific heuristics in tension:
|
||||
- Which principle says do A?
|
||||
- Which principle says do B?
|
||||
- Why can't we fully satisfy both?
|
||||
|
||||
### Step 2: Assess the Context
|
||||
|
||||
Consider:
|
||||
- **User type:** Novice vs. expert
|
||||
- **Task criticality:** Browsing vs. financial transaction
|
||||
- **Frequency:** One-time vs. daily
|
||||
- **Reversibility:** Can they undo?
|
||||
- **Consequence:** What happens if wrong?
|
||||
|
||||
### Step 3: Prioritize for This Context
|
||||
|
||||
| Context | Usually Prioritize |
|
||||
|---------|-------------------|
|
||||
| Critical data, irreversible | Error prevention > Efficiency |
|
||||
| Frequent actions, low stakes | Efficiency > Error prevention |
|
||||
| New users, unfamiliar domain | Guidance > Control |
|
||||
| Expert users, familiar domain | Flexibility > Simplicity |
|
||||
| Limited screen, focused task | Minimalism > Discoverability |
|
||||
| Learning interface | Discoverability > Minimalism |
|
||||
|
||||
### Step 4: Design for Both When Possible
|
||||
|
||||
Often, clever design satisfies both:
|
||||
|
||||
| Conflict | Both-And Solution |
|
||||
|----------|-------------------|
|
||||
| Simple vs. Powerful | Progressive disclosure |
|
||||
| Efficient vs. Safe | Undo instead of confirm |
|
||||
| Clean vs. Discoverable | Contextual reveal |
|
||||
| Consistent vs. Optimal | Consistent patterns, variable emphasis |
|
||||
| Guided vs. Controlled | Warnings, not blockers |
|
||||
|
||||
### Step 5: Test the Trade-off
|
||||
|
||||
Validate with real users:
|
||||
- Does the chosen priority work for target users?
|
||||
- Are edge cases handled acceptably?
|
||||
- Do users understand why limitations exist?
|
||||
|
||||
---
|
||||
|
||||
## Common Conflict Scenarios
|
||||
|
||||
### Scenario: Onboarding Flow
|
||||
|
||||
**Conflict:** Efficiency (skip it) vs. Guidance (require it)
|
||||
|
||||
**Resolution:**
|
||||
- Allow skip but show value
|
||||
- Defer to contextual moments
|
||||
- Progressive onboarding during natural use
|
||||
|
||||
### Scenario: Mobile Navigation
|
||||
|
||||
**Conflict:** Discoverability (show all options) vs. Minimalism (hamburger menu)
|
||||
|
||||
**Resolution:**
|
||||
- Bottom navigation for 3-5 key items
|
||||
- Hamburger for secondary items
|
||||
- Tab bar > hamburger for primary navigation
|
||||
|
||||
### Scenario: Form Validation
|
||||
|
||||
**Conflict:** Error prevention (validate immediately) vs. Efficiency (let them type)
|
||||
|
||||
**Resolution:**
|
||||
- Validate on blur (not on keystroke)
|
||||
- Show errors inline, not modal
|
||||
- Allow submission, show all errors
|
||||
|
||||
### Scenario: Confirmation Dialogs
|
||||
|
||||
**Conflict:** Error prevention (confirm everything) vs. Efficiency (just do it)
|
||||
|
||||
**Resolution:**
|
||||
- Confirm only irreversible/high-cost actions
|
||||
- Provide undo instead of confirm
|
||||
- Use clear language about consequences
|
||||
|
||||
### Scenario: Default Settings
|
||||
|
||||
**Conflict:** User control (let them configure) vs. Simplicity (sensible defaults)
|
||||
|
||||
**Resolution:**
|
||||
- Smart defaults that work for 80%
|
||||
- Easy access to change settings
|
||||
- Don't require configuration to start
|
||||
|
||||
---
|
||||
|
||||
## Decision Documentation
|
||||
|
||||
When making trade-off decisions, document:
|
||||
|
||||
```markdown
|
||||
## Design Decision: [Feature]
|
||||
|
||||
### Conflict
|
||||
[Heuristic A] suggests we should...
|
||||
[Heuristic B] suggests we should...
|
||||
|
||||
### Context
|
||||
- User type: [Novice/Expert]
|
||||
- Task: [Critical/Casual]
|
||||
- Frequency: [Daily/Occasional]
|
||||
- Reversibility: [Yes/No]
|
||||
|
||||
### Decision
|
||||
We chose to prioritize [Heuristic] because...
|
||||
|
||||
### Mitigation
|
||||
We addressed the other concern by...
|
||||
|
||||
### Validation
|
||||
We'll know this is right when...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Principles
|
||||
|
||||
### 1. Context Is King
|
||||
|
||||
The same conflict should be resolved differently in different contexts. A banking app and a social media app may reach opposite conclusions.
|
||||
|
||||
### 2. Know Your Users
|
||||
|
||||
The trade-off depends heavily on user expertise and expectations. Test with real users, not assumptions.
|
||||
|
||||
### 3. Design for Both When Possible
|
||||
|
||||
Often, the conflict is artificial and clever design can satisfy both principles.
|
||||
|
||||
### 4. Make Conscious Trade-offs
|
||||
|
||||
Don't accidentally violate a heuristic. Violate it consciously with reasoning.
|
||||
|
||||
### 5. Document Decisions
|
||||
|
||||
Future you (and your team) will want to know why choices were made.
|
||||
|
||||
### 6. Revisit Trade-offs
|
||||
|
||||
As products and users evolve, optimal trade-offs may shift. Periodically review past decisions.
|
||||
@@ -0,0 +1,289 @@
|
||||
# Krug's Usability Principles
|
||||
|
||||
Full methodology from "Don't Make Me Think" for creating intuitive, usable interfaces.
|
||||
|
||||
## The Reality of How Users Use the Web
|
||||
|
||||
### Fact 1: We Don't Read, We Scan
|
||||
|
||||
**Why users scan:**
|
||||
- They're on a mission (have a goal)
|
||||
- They know they don't need to read everything
|
||||
- They're good at scanning from years of practice
|
||||
|
||||
**Design implications:**
|
||||
- Create visual hierarchy (important things bigger, bolder)
|
||||
- Use headings and subheadings liberally
|
||||
- Keep paragraphs short
|
||||
- Use bulleted lists
|
||||
- Highlight key terms
|
||||
|
||||
### Fact 2: We Don't Make Optimal Choices, We Satisfice
|
||||
|
||||
**Satisficing** = "satisfying" + "sufficing"
|
||||
|
||||
Users click the first reasonable option, not the best option.
|
||||
|
||||
**Why satisficing:**
|
||||
- Optimizing is hard and takes time
|
||||
- The penalty for guessing wrong is low (back button)
|
||||
- Weighing options doesn't improve results much
|
||||
- Guessing is more fun
|
||||
|
||||
**Design implications:**
|
||||
- Make the right choice obvious
|
||||
- Don't rely on users reading all options
|
||||
- Make consequences clear before clicking
|
||||
|
||||
### Fact 3: We Don't Figure Out How Things Work, We Muddle Through
|
||||
|
||||
Users don't read instructions. They try things until something works.
|
||||
|
||||
**Why muddling through:**
|
||||
- It's not important to them to know how it works
|
||||
- If they find something that works, they stick with it
|
||||
- It rarely matters if they don't use something optimally
|
||||
|
||||
**Design implications:**
|
||||
- Make things obvious so "figuring out" isn't needed
|
||||
- Expect users to use things "wrong"
|
||||
- Design for recovery from mistakes
|
||||
|
||||
---
|
||||
|
||||
## Things You Should Never Do
|
||||
|
||||
### Don't Make Users Think
|
||||
|
||||
Signs that your page makes users think:
|
||||
|
||||
| Symptom | Example |
|
||||
|---------|---------|
|
||||
| Puzzling labels | "Solutions" (solutions to what?) |
|
||||
| Links that could go anywhere | "Click here" |
|
||||
| Unexplained options | Checkboxes without context |
|
||||
| Unfamiliar terminology | Industry jargon |
|
||||
| Gratuitous cleverness | Puns, wordplay in navigation |
|
||||
|
||||
### Don't Waste Users' Goodwill
|
||||
|
||||
Users have a finite reservoir of goodwill:
|
||||
|
||||
**Things that diminish goodwill:**
|
||||
- Hiding info they need (phone numbers, prices)
|
||||
- Punishing them for not doing things your way
|
||||
- Asking for unnecessary information
|
||||
- Making them feel stupid
|
||||
- Making them repeat themselves
|
||||
- Sites that look like an afterthought
|
||||
- Amateur errors (broken links, typos)
|
||||
|
||||
**Things that increase goodwill:**
|
||||
- Know what questions they have and answer them
|
||||
- Minimize steps
|
||||
- Put effort into UI quality
|
||||
- Know what they're likely to struggle with
|
||||
- Make it easy to recover from errors
|
||||
- Apologize when things go wrong
|
||||
|
||||
### Don't Make Words Seem Important If They're Not
|
||||
|
||||
**Happy talk example:**
|
||||
> "Welcome to our website! We're excited to help you find exactly what you're looking for. Our team of dedicated professionals is committed to providing you with the best possible experience."
|
||||
|
||||
**Reality:** Users skip this. It says nothing.
|
||||
|
||||
**Rule:** If users will skip it, remove it.
|
||||
|
||||
---
|
||||
|
||||
## Navigation Must-Haves
|
||||
|
||||
### The Permanent Navigation
|
||||
|
||||
Every page needs:
|
||||
|
||||
1. **Site ID** (logo/name) - Top left corner
|
||||
2. **Page name** - Prominent, matches link that brought them
|
||||
3. **Sections** - Major site areas
|
||||
4. **Local navigation** - What's in this section
|
||||
5. **Utilities** - Sign in, Search, Help, Cart
|
||||
6. **"You are here" indicator** - Highlighted nav item
|
||||
|
||||
### The Trunk Test
|
||||
|
||||
Can users answer these on any random page?
|
||||
|
||||
| Question | Element That Answers It |
|
||||
|----------|------------------------|
|
||||
| What site is this? | Logo/Site ID |
|
||||
| What page am I on? | Page title |
|
||||
| What major sections exist? | Main navigation |
|
||||
| What are my options here? | Local navigation |
|
||||
| Where am I in the structure? | Breadcrumbs, highlighted nav |
|
||||
| How can I search? | Search box |
|
||||
|
||||
### Breadcrumbs
|
||||
|
||||
**Good breadcrumbs:**
|
||||
```
|
||||
Home > Products > Shoes > Running > Men's Trail Runners
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Put them at the top
|
||||
- Use ">" between levels
|
||||
- Make the current page name visible but not a link
|
||||
- Use small text (secondary importance)
|
||||
|
||||
---
|
||||
|
||||
## Homepage Guidelines
|
||||
|
||||
The homepage has to do too many things:
|
||||
|
||||
1. Site identity and mission
|
||||
2. Site hierarchy (navigation)
|
||||
3. Search
|
||||
4. Teases (content, features)
|
||||
5. Timely content
|
||||
6. Deals
|
||||
7. Shortcuts (popular items)
|
||||
8. Registration/Sign in
|
||||
|
||||
### Homepage Priorities
|
||||
|
||||
**Must do:**
|
||||
- Tell me what this site is
|
||||
- Tell me what I can do here
|
||||
- Tell me why I should be here (and not somewhere else)
|
||||
- Start me on my way
|
||||
|
||||
**Should do:**
|
||||
- Show me what I'm looking for
|
||||
- Show me where to start
|
||||
- Establish credibility and trust
|
||||
|
||||
### The Big Bang Theory of Web Design
|
||||
|
||||
**You have 3-4 seconds to answer:**
|
||||
1. What is this?
|
||||
2. What can I do here?
|
||||
3. Why should I be here?
|
||||
|
||||
### Tagline Guidelines
|
||||
|
||||
| Good Tagline | Bad Tagline |
|
||||
|--------------|-------------|
|
||||
| Conveys unique value | Generic platitude |
|
||||
| Specific and informative | Vague and fluffy |
|
||||
| 6-8 words | Too long or too short |
|
||||
| Instantly understandable | Requires thought |
|
||||
|
||||
Examples:
|
||||
- Good: "Find anything from thousands of stores"
|
||||
- Bad: "Welcome to the future of shopping"
|
||||
|
||||
---
|
||||
|
||||
## Mobile Usability
|
||||
|
||||
### Mobile Considerations
|
||||
|
||||
**Constraints:**
|
||||
- Smaller viewport
|
||||
- Fat fingers (need bigger targets)
|
||||
- Single-column layout
|
||||
- No hover states
|
||||
- Variable attention and context
|
||||
|
||||
### Mobile Specifics
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Tiny tap targets | Minimum 44×44 px |
|
||||
| Crowded nav | Hamburger or bottom nav |
|
||||
| Long forms | Break into steps |
|
||||
| Hover-dependent UI | Alternative for touch |
|
||||
| Text too small | 16px minimum body text |
|
||||
|
||||
### Mobile Trade-offs
|
||||
|
||||
**What to prioritize:**
|
||||
- Primary tasks front and center
|
||||
- Essential content visible
|
||||
- Fast load times
|
||||
- Easy to tap, hard to mis-tap
|
||||
|
||||
**What to hide/remove:**
|
||||
- Secondary navigation
|
||||
- Non-essential images
|
||||
- Decorative elements
|
||||
- Long-form content
|
||||
|
||||
---
|
||||
|
||||
## Usability Testing on $0
|
||||
|
||||
### How Many Users?
|
||||
|
||||
**3-4 users catches most issues.**
|
||||
|
||||
Testing with more users has diminishing returns. Better to test with 3, fix issues, then test again with 3.
|
||||
|
||||
### What to Test
|
||||
|
||||
1. Can they complete core tasks?
|
||||
2. Where do they get stuck?
|
||||
3. What do they say out loud?
|
||||
4. What did they expect vs. what happened?
|
||||
|
||||
### Test Protocol
|
||||
|
||||
**Before:**
|
||||
- "I'm testing the site, not you"
|
||||
- "Think out loud as you go"
|
||||
- "There are no wrong answers"
|
||||
|
||||
**During:**
|
||||
- Don't help. Don't explain. Just watch.
|
||||
- Note hesitations and confusions
|
||||
- Write down what they say
|
||||
|
||||
**After:**
|
||||
- Ask what was confusing
|
||||
- Ask what they expected
|
||||
- Ask how they'd describe the site
|
||||
|
||||
### Common Findings
|
||||
|
||||
| What Users Do | What It Means |
|
||||
|---------------|---------------|
|
||||
| Click wrong thing | Label is confusing |
|
||||
| Hesitate | Decision isn't obvious |
|
||||
| Look around lost | "You are here" is unclear |
|
||||
| Read everything | Design isn't self-evident |
|
||||
| Use search immediately | Navigation is failing |
|
||||
| Express confusion | Copy is unclear |
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Basics
|
||||
|
||||
### Why It Matters
|
||||
|
||||
- 15-20% of population has some disability
|
||||
- Accessible sites are better for everyone
|
||||
- It's often required by law
|
||||
- It's the right thing to do
|
||||
|
||||
### Quick Wins
|
||||
|
||||
| Fix | Benefit |
|
||||
|-----|---------|
|
||||
| Add alt text to images | Screen readers can describe |
|
||||
| Use sufficient contrast | Low-vision users can read |
|
||||
| Allow keyboard navigation | Motor-impaired users can navigate |
|
||||
| Use semantic HTML | Assistive tech understands structure |
|
||||
| Add focus indicators | Keyboard users know where they are |
|
||||
| Make touch targets large | Everyone benefits |
|
||||
@@ -0,0 +1,360 @@
|
||||
# Nielsen's 10 Usability Heuristics
|
||||
|
||||
Jakob Nielsen's 10 general principles for interaction design, with practical examples and common violations.
|
||||
|
||||
---
|
||||
|
||||
## 1. Visibility of System Status
|
||||
|
||||
**Principle:** The system should always keep users informed about what is going on, through appropriate feedback within reasonable time.
|
||||
|
||||
### Examples of Good Implementation
|
||||
|
||||
| Situation | Good Feedback |
|
||||
|-----------|---------------|
|
||||
| File uploading | Progress bar with percentage |
|
||||
| Form submitted | "Your message has been sent" |
|
||||
| Action processing | Loading spinner |
|
||||
| Background task | "Syncing 3 files..." notification |
|
||||
| Successful action | Green checkmark confirmation |
|
||||
|
||||
### Common Violations
|
||||
|
||||
| Violation | Problem | Fix |
|
||||
|-----------|---------|-----|
|
||||
| No loading indicator | User thinks it's broken | Add spinner/progress |
|
||||
| Silent failures | User thinks action worked | Show error message |
|
||||
| Delayed feedback | User clicks again | Immediate visual response |
|
||||
| No confirmation | "Did that work?" | Confirm successful actions |
|
||||
| Hidden status | User can't find progress | Surface status prominently |
|
||||
|
||||
### Severity Examples
|
||||
|
||||
- **Minor (1):** Save button has no "saved" confirmation
|
||||
- **Major (3):** Payment processing with no indicator
|
||||
- **Catastrophic (4):** Form submit shows nothing, user submits multiple times
|
||||
|
||||
---
|
||||
|
||||
## 2. Match Between System and Real World
|
||||
|
||||
**Principle:** The system should speak the users' language, with words, phrases, and concepts familiar to the user, rather than system-oriented terms.
|
||||
|
||||
### Examples of Good Implementation
|
||||
|
||||
| System Term | User-Friendly Term |
|
||||
|-------------|-------------------|
|
||||
| Authenticate | Sign in |
|
||||
| Terminate | Cancel / End |
|
||||
| Query | Search |
|
||||
| Repository | Folder |
|
||||
| Navigate to | Go to |
|
||||
| Initiate | Start |
|
||||
|
||||
### Real-World Metaphors
|
||||
|
||||
| Digital Element | Real-World Match |
|
||||
|-----------------|------------------|
|
||||
| Trash/Recycle bin | Waste basket |
|
||||
| Folder | File folder |
|
||||
| Desktop | Physical desk |
|
||||
| Shopping cart | Store cart |
|
||||
| Bookmark | Physical bookmark |
|
||||
|
||||
### Common Violations
|
||||
|
||||
| Violation | Problem | Fix |
|
||||
|-----------|---------|-----|
|
||||
| Technical jargon | Confusion | Use plain language |
|
||||
| Internal names | Meaningless to users | User-tested labels |
|
||||
| Inconsistent terms | Same thing, different names | One term per concept |
|
||||
| Unfamiliar icons | Users guess wrong | Add labels or tooltips |
|
||||
| Illogical order | Not matching expectations | Follow real-world sequences |
|
||||
|
||||
---
|
||||
|
||||
## 3. User Control and Freedom
|
||||
|
||||
**Principle:** Users often choose system functions by mistake and need a clearly marked "emergency exit" to leave the unwanted state without having to go through an extended dialogue.
|
||||
|
||||
### Examples of Good Implementation
|
||||
|
||||
| Action | Escape Route |
|
||||
|--------|--------------|
|
||||
| Accidentally deleted email | Undo button (Gmail) |
|
||||
| Wrong menu opened | Click outside to close |
|
||||
| Filled form incorrectly | Clear form / Reset |
|
||||
| Navigated wrong | Back button works |
|
||||
| Started wrong workflow | Cancel / Exit anytime |
|
||||
|
||||
### Common Violations
|
||||
|
||||
| Violation | Problem | Fix |
|
||||
|-----------|---------|-----|
|
||||
| No undo | Users afraid to act | Add undo for all actions |
|
||||
| Forced wizards | Can't skip or go back | Allow non-linear navigation |
|
||||
| Modal traps | Can't escape | Clear close/cancel buttons |
|
||||
| Broken back button | Frustration | Never hijack browser history |
|
||||
| Immediate deletion | No recovery | Soft delete + undo option |
|
||||
|
||||
### Key Principle
|
||||
|
||||
**Undo > Confirmation dialogs**
|
||||
|
||||
Users click through "Are you sure?" without reading. Undo lets them act confidently.
|
||||
|
||||
---
|
||||
|
||||
## 4. Consistency and Standards
|
||||
|
||||
**Principle:** Users should not have to wonder whether different words, situations, or actions mean the same thing. Follow platform conventions.
|
||||
|
||||
### Types of Consistency
|
||||
|
||||
| Type | Example |
|
||||
|------|---------|
|
||||
| **Internal** | Same button style throughout your app |
|
||||
| **External** | Same patterns as other apps |
|
||||
| **Visual** | Same colors mean same things |
|
||||
| **Functional** | Same action = same result |
|
||||
| **Linguistic** | Same terms for same concepts |
|
||||
|
||||
### Platform Conventions
|
||||
|
||||
| Element | Convention |
|
||||
|---------|------------|
|
||||
| Logo | Top left, links to home |
|
||||
| Search | Top right, magnifying glass |
|
||||
| Cart | Top right, shopping cart icon |
|
||||
| Menu (mobile) | Hamburger icon |
|
||||
| Primary action | Right side or bottom of form |
|
||||
| Cancel | Left of primary action (or text link) |
|
||||
|
||||
### Common Violations
|
||||
|
||||
| Violation | Problem | Fix |
|
||||
|-----------|---------|-----|
|
||||
| Different button styles | Confusion about importance | Consistent button hierarchy |
|
||||
| Same word, different meanings | Misunderstanding | One term per concept |
|
||||
| Unexpected link behavior | New tab when expecting same tab | Follow conventions |
|
||||
| Non-standard icons | Guessing game | Use recognized icons |
|
||||
| Inconsistent layouts | Relearning each page | Template-based layouts |
|
||||
|
||||
---
|
||||
|
||||
## 5. Error Prevention
|
||||
|
||||
**Principle:** Even better than good error messages is a careful design which prevents a problem from occurring in the first place.
|
||||
|
||||
### Prevention Strategies
|
||||
|
||||
| Strategy | Example |
|
||||
|----------|---------|
|
||||
| **Constraints** | Date picker instead of text field |
|
||||
| **Suggestions** | Autocomplete |
|
||||
| **Defaults** | Pre-fill common values |
|
||||
| **Confirmation** | "Delete permanently?" for destructive actions |
|
||||
| **Warnings** | "Unsaved changes" before leaving |
|
||||
|
||||
### Types of Errors to Prevent
|
||||
|
||||
| Error Type | Prevention |
|
||||
|------------|------------|
|
||||
| **Slips** (accidental) | Confirmation, undo, large targets |
|
||||
| **Mistakes** (wrong intention) | Clear instructions, better defaults |
|
||||
| **Data errors** | Validation, formatting help |
|
||||
| **Navigation errors** | Clear labels, undo |
|
||||
|
||||
### Common Violations
|
||||
|
||||
| Violation | Problem | Fix |
|
||||
|-----------|---------|-----|
|
||||
| Free text for constrained data | Invalid entries | Dropdowns, pickers |
|
||||
| No save warning | Lost work | "Unsaved changes" prompt |
|
||||
| Easy destructive actions | Accidental deletion | Require confirmation |
|
||||
| Accepting bad input | Garbage data | Inline validation |
|
||||
| Ambiguous choices | Wrong selection | Clear differentiation |
|
||||
|
||||
---
|
||||
|
||||
## 6. Recognition Rather Than Recall
|
||||
|
||||
**Principle:** Minimize the user's memory load by making objects, actions, and options visible. Don't require users to remember information.
|
||||
|
||||
### Recognition Techniques
|
||||
|
||||
| Instead of | Do This |
|
||||
|------------|---------|
|
||||
| User remembers command | Show menu of options |
|
||||
| User types from memory | Dropdown/autocomplete |
|
||||
| User remembers where they were | Breadcrumbs, recent history |
|
||||
| User remembers codes | Show decoded values |
|
||||
| User recalls previous info | Show previous entries |
|
||||
|
||||
### Examples
|
||||
|
||||
| Bad (Recall) | Good (Recognition) |
|
||||
|--------------|-------------------|
|
||||
| "Enter country code" | Dropdown with country names |
|
||||
| Command-line interface | Graphical menus |
|
||||
| "See page 47 for options" | Options shown in context |
|
||||
| "Re-enter your email" | Pre-filled from previous step |
|
||||
| Complex keyboard shortcuts | Visible toolbar buttons |
|
||||
|
||||
### Common Violations
|
||||
|
||||
| Violation | Problem | Fix |
|
||||
|-----------|---------|-----|
|
||||
| Empty form fields | User must remember format | Placeholder examples |
|
||||
| Hidden actions | User forgets they exist | Keep visible or in menus |
|
||||
| No recent items | User re-searches | Show search history |
|
||||
| Unlabeled icons | User guesses meaning | Add text labels |
|
||||
| Disconnected workflows | User loses context | Show progress, breadcrumbs |
|
||||
|
||||
---
|
||||
|
||||
## 7. Flexibility and Efficiency of Use
|
||||
|
||||
**Principle:** Accelerators—unseen by the novice user—may often speed up the interaction for the expert user. Allow users to tailor frequent actions.
|
||||
|
||||
### Accelerators for Experts
|
||||
|
||||
| Feature | Example |
|
||||
|---------|---------|
|
||||
| **Keyboard shortcuts** | Ctrl+S to save |
|
||||
| **Touch gestures** | Swipe to archive |
|
||||
| **Recent/Favorites** | Quick access to common items |
|
||||
| **Saved searches** | One-click complex queries |
|
||||
| **Customization** | Personalized dashboard |
|
||||
| **Bulk actions** | Select all + action |
|
||||
|
||||
### Progressive Disclosure
|
||||
|
||||
| User Level | What They See |
|
||||
|------------|---------------|
|
||||
| Novice | Essential features only |
|
||||
| Intermediate | Common advanced options |
|
||||
| Expert | Full power (shortcuts, customization) |
|
||||
|
||||
### Common Violations
|
||||
|
||||
| Violation | Problem | Fix |
|
||||
|-----------|---------|-----|
|
||||
| No shortcuts | Experts slowed down | Add keyboard shortcuts |
|
||||
| No bulk operations | Tedious repetition | Add multi-select |
|
||||
| Required tutorials | Experts frustrated | Allow skipping |
|
||||
| Hidden power features | Experts don't find them | Discoverable advanced mode |
|
||||
| No customization | Forced workflows | Allow personalization |
|
||||
|
||||
---
|
||||
|
||||
## 8. Aesthetic and Minimalist Design
|
||||
|
||||
**Principle:** Dialogues should not contain information which is irrelevant or rarely needed. Every extra unit of information competes with the relevant units.
|
||||
|
||||
### Principles
|
||||
|
||||
| Principle | Application |
|
||||
|-----------|-------------|
|
||||
| **Signal/Noise** | Increase signal, reduce noise |
|
||||
| **Visual hierarchy** | Important things stand out |
|
||||
| **Whitespace** | Give elements room to breathe |
|
||||
| **Content priority** | Show what matters, hide what doesn't |
|
||||
| **Progressive disclosure** | Complexity on demand |
|
||||
|
||||
### What to Remove
|
||||
|
||||
| Remove | Why |
|
||||
|--------|-----|
|
||||
| Rarely-used features | Clutter |
|
||||
| Decorative elements | Distraction |
|
||||
| Redundant text | Noise |
|
||||
| Unnecessary options | Decision fatigue |
|
||||
| Instructions users skip | Wasted space |
|
||||
|
||||
### Common Violations
|
||||
|
||||
| Violation | Problem | Fix |
|
||||
|-----------|---------|-----|
|
||||
| Cluttered screens | Overwhelming | Remove/hide secondary |
|
||||
| Everything is "important" | Nothing stands out | Create hierarchy |
|
||||
| Long blocks of text | Nobody reads | Break up, summarize |
|
||||
| Too many colors | Visual noise | Limit palette |
|
||||
| Dense layouts | Hard to scan | Add whitespace |
|
||||
|
||||
---
|
||||
|
||||
## 9. Help Users Recognize, Diagnose, and Recover from Errors
|
||||
|
||||
**Principle:** Error messages should be expressed in plain language (no codes), precisely indicate the problem, and constructively suggest a solution.
|
||||
|
||||
### Good Error Message Components
|
||||
|
||||
1. **What happened** (plain language)
|
||||
2. **Why it happened** (if helpful)
|
||||
3. **How to fix it** (specific action)
|
||||
|
||||
### Examples
|
||||
|
||||
| Bad Error | Good Error |
|
||||
|-----------|------------|
|
||||
| "Error 500" | "Something went wrong. Please try again." |
|
||||
| "Invalid input" | "Email must include @" |
|
||||
| "Failed" | "Payment declined. Check card number or try different card." |
|
||||
| "Null reference exception" | "We couldn't load your data. Refresh the page." |
|
||||
|
||||
### Error Message Guidelines
|
||||
|
||||
| Guideline | Example |
|
||||
|-----------|---------|
|
||||
| Use plain language | "Connection failed" not "ECONNREFUSED" |
|
||||
| Be specific | "Password too short" not "Invalid password" |
|
||||
| Provide action | "Try again" button visible |
|
||||
| Don't blame user | "Card declined" not "You entered wrong info" |
|
||||
| Maintain context | Keep filled data, highlight error field |
|
||||
|
||||
### Common Violations
|
||||
|
||||
| Violation | Problem | Fix |
|
||||
|-----------|---------|-----|
|
||||
| Technical jargon | Confusion | Translate to plain English |
|
||||
| No solution | User stuck | Include next steps |
|
||||
| Generic messages | Not helpful | Be specific |
|
||||
| Blaming language | Defensive users | Neutral, helpful tone |
|
||||
| Clearing form on error | Punishment | Preserve user input |
|
||||
|
||||
---
|
||||
|
||||
## 10. Help and Documentation
|
||||
|
||||
**Principle:** Even though it's better if the system can be used without documentation, it may be necessary to provide help and documentation. Such information should be easy to search, focused on the user's task, and not be too large.
|
||||
|
||||
### Characteristics of Good Help
|
||||
|
||||
| Characteristic | Implementation |
|
||||
|----------------|----------------|
|
||||
| **Searchable** | Full-text search |
|
||||
| **Task-focused** | "How to..." format |
|
||||
| **Contextual** | In-page tooltips |
|
||||
| **Scannable** | Short paragraphs, lists |
|
||||
| **Actionable** | Step-by-step instructions |
|
||||
|
||||
### Types of Help
|
||||
|
||||
| Type | When to Use |
|
||||
|------|-------------|
|
||||
| **Inline help** | Tooltips, hints | Next to complex fields |
|
||||
| **Contextual help** | "?" icons | For non-obvious features |
|
||||
| **Searchable docs** | Knowledge base | For detailed questions |
|
||||
| **Guided tours** | Onboarding | First-time users |
|
||||
| **Chat/Support** | Complex issues | When self-service fails |
|
||||
|
||||
### Common Violations
|
||||
|
||||
| Violation | Problem | Fix |
|
||||
|-----------|---------|-----|
|
||||
| No search in docs | Can't find answers | Add search |
|
||||
| Long documentation | Nobody reads | Concise, task-focused |
|
||||
| Generic help | Doesn't answer question | Specific to feature/page |
|
||||
| Hidden help | Users can't find it | Visible help links |
|
||||
| No contextual help | Users leave page | Inline tooltips |
|
||||
@@ -0,0 +1,306 @@
|
||||
# WCAG 2.1 AA Checklist
|
||||
|
||||
Complete checklist for WCAG 2.1 Level AA compliance with testing guidance.
|
||||
|
||||
## Perceivable
|
||||
|
||||
Content must be presentable in ways users can perceive.
|
||||
|
||||
### 1.1 Text Alternatives
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 1.1.1 Non-text Content | All images, icons, and visual content have text alternatives | Inspect alt attributes; use screen reader |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] Informative images have descriptive alt text
|
||||
- [ ] Decorative images have empty alt (`alt=""`)
|
||||
- [ ] Complex images (charts, diagrams) have extended descriptions
|
||||
- [ ] Icons have accessible names
|
||||
- [ ] CAPTCHA provides audio alternative
|
||||
|
||||
### 1.2 Time-based Media
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 1.2.1 Audio/Video (prerecorded) | Captions and/or transcripts | Check video player for captions |
|
||||
| 1.2.2 Captions | Synchronized captions for video | Watch with captions on |
|
||||
| 1.2.3 Audio Description | Description of visual content | Check for AD track |
|
||||
| 1.2.5 Audio Description (AA) | Audio description for all video | Verify AD available |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] Videos have synchronized captions
|
||||
- [ ] Captions are accurate and complete
|
||||
- [ ] Audio descriptions available for important visual content
|
||||
- [ ] Transcripts available for audio-only content
|
||||
|
||||
### 1.3 Adaptable
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 1.3.1 Info and Relationships | Semantic structure preserved | Inspect HTML; use screen reader |
|
||||
| 1.3.2 Meaningful Sequence | Content order makes sense | Disable CSS; read in DOM order |
|
||||
| 1.3.3 Sensory Characteristics | Don't rely on shape/color/position alone | Check instructions |
|
||||
| 1.3.4 Orientation (AA) | Works in portrait and landscape | Rotate device |
|
||||
| 1.3.5 Identify Input Purpose (AA) | Input fields have autocomplete | Check `autocomplete` attributes |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] Headings use proper h1-h6 hierarchy
|
||||
- [ ] Lists use `<ul>`, `<ol>`, `<dl>` elements
|
||||
- [ ] Forms have proper labels associated with inputs
|
||||
- [ ] Tables have headers marked with `<th>`
|
||||
- [ ] Reading order is logical without CSS
|
||||
- [ ] Works in both orientations
|
||||
- [ ] Input fields have appropriate autocomplete values
|
||||
|
||||
### 1.4 Distinguishable
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 1.4.1 Use of Color | Color not sole means of info | View in grayscale |
|
||||
| 1.4.2 Audio Control | Auto-playing audio can be stopped | Check for controls |
|
||||
| 1.4.3 Contrast (Minimum) | 4.5:1 text, 3:1 large text | Use contrast checker |
|
||||
| 1.4.4 Resize Text | Readable at 200% zoom | Zoom browser |
|
||||
| 1.4.5 Images of Text | Use real text, not images | Inspect for text images |
|
||||
| 1.4.10 Reflow (AA) | No horizontal scroll at 320px | Test at narrow width |
|
||||
| 1.4.11 Non-text Contrast (AA) | 3:1 for UI components | Check buttons, inputs |
|
||||
| 1.4.12 Text Spacing (AA) | Survives increased text spacing | Apply spacing override |
|
||||
| 1.4.13 Content on Hover/Focus (AA) | Hoverable, dismissible, persistent | Test tooltips, menus |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] Normal text has 4.5:1 contrast ratio minimum
|
||||
- [ ] Large text (18pt+) has 3:1 contrast ratio minimum
|
||||
- [ ] UI components have 3:1 contrast
|
||||
- [ ] Text resizes to 200% without loss
|
||||
- [ ] No horizontal scrolling at 320px width
|
||||
- [ ] Tooltips are dismissible, hoverable, persistent
|
||||
- [ ] Color alone doesn't convey meaning
|
||||
|
||||
---
|
||||
|
||||
## Operable
|
||||
|
||||
Users must be able to operate the interface.
|
||||
|
||||
### 2.1 Keyboard Accessible
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 2.1.1 Keyboard | All functionality works with keyboard | Navigate with Tab, Enter, Space |
|
||||
| 2.1.2 No Keyboard Trap | Focus can always move away | Tab through everything |
|
||||
| 2.1.4 Character Key Shortcuts (AA) | Single-key shortcuts can be disabled | Check for shortcut conflicts |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] All interactive elements are focusable
|
||||
- [ ] All actions can be performed via keyboard
|
||||
- [ ] Focus is never trapped
|
||||
- [ ] Single-key shortcuts can be disabled or remapped
|
||||
- [ ] Tab order follows logical sequence
|
||||
|
||||
### 2.2 Enough Time
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 2.2.1 Timing Adjustable | Users can extend/disable time limits | Check for timeouts |
|
||||
| 2.2.2 Pause, Stop, Hide | Auto-updating content can be controlled | Check carousels, animations |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] Session timeouts have 20-second warning
|
||||
- [ ] Users can extend time limits
|
||||
- [ ] Auto-playing content has pause control
|
||||
- [ ] Animations can be stopped
|
||||
|
||||
### 2.3 Seizures
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 2.3.1 Three Flashes | No content flashes >3 times/second | Measure flash rate |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] No flashing content above threshold
|
||||
- [ ] Animations don't cause seizure risk
|
||||
|
||||
### 2.4 Navigable
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 2.4.1 Bypass Blocks | Skip links to bypass repeated content | Check for skip link |
|
||||
| 2.4.2 Page Titled | Pages have descriptive titles | Check `<title>` element |
|
||||
| 2.4.3 Focus Order | Focus sequence is logical | Tab through page |
|
||||
| 2.4.4 Link Purpose | Link text describes destination | Read links out of context |
|
||||
| 2.4.5 Multiple Ways (AA) | Multiple ways to find pages | Check nav, search, sitemap |
|
||||
| 2.4.6 Headings and Labels (AA) | Headings and labels are descriptive | Review all headings |
|
||||
| 2.4.7 Focus Visible (AA) | Focus indicator is visible | Tab through interface |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] Skip link present at top of page
|
||||
- [ ] Page titles are unique and descriptive
|
||||
- [ ] Tab order follows visual order
|
||||
- [ ] Link text is meaningful ("Read more about X" not "Click here")
|
||||
- [ ] Multiple navigation methods available
|
||||
- [ ] All headings are descriptive
|
||||
- [ ] Focus indicator is clearly visible
|
||||
|
||||
### 2.5 Input Modalities
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 2.5.1 Pointer Gestures (AA) | Complex gestures have simple alternatives | Check for alternatives |
|
||||
| 2.5.2 Pointer Cancellation (AA) | Actions on up-event, cancelable | Test click/drag behavior |
|
||||
| 2.5.3 Label in Name (AA) | Accessible name contains visible label | Compare visible/accessible names |
|
||||
| 2.5.4 Motion Actuation (AA) | Motion-based actions have alternatives | Check for non-motion options |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] Pinch, swipe have tap alternatives
|
||||
- [ ] Click actions happen on release (up-event)
|
||||
- [ ] Dragging outside target cancels action
|
||||
- [ ] Accessible names include visible text
|
||||
- [ ] Shake/tilt actions have button alternatives
|
||||
|
||||
---
|
||||
|
||||
## Understandable
|
||||
|
||||
Content must be understandable to users.
|
||||
|
||||
### 3.1 Readable
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 3.1.1 Language of Page | Page language specified | Check `<html lang="">` |
|
||||
| 3.1.2 Language of Parts (AA) | Foreign text marked with lang | Check multilingual content |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] HTML has lang attribute
|
||||
- [ ] Foreign language passages have lang attribute
|
||||
|
||||
### 3.2 Predictable
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 3.2.1 On Focus | Focus doesn't cause unexpected changes | Tab through all elements |
|
||||
| 3.2.2 On Input | Input doesn't cause unexpected changes | Fill forms without submitting |
|
||||
| 3.2.3 Consistent Navigation (AA) | Navigation is consistent across pages | Compare pages |
|
||||
| 3.2.4 Consistent Identification (AA) | Same functions have same labels | Compare repeated elements |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] Focus doesn't trigger context changes
|
||||
- [ ] Selecting options doesn't submit forms
|
||||
- [ ] Navigation is in same location on all pages
|
||||
- [ ] Same icons/labels used for same functions
|
||||
|
||||
### 3.3 Input Assistance
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 3.3.1 Error Identification | Errors clearly identified | Submit invalid forms |
|
||||
| 3.3.2 Labels or Instructions | Labels and instructions provided | Review all forms |
|
||||
| 3.3.3 Error Suggestion (AA) | Suggestions for fixing errors | Trigger errors |
|
||||
| 3.3.4 Error Prevention (AA) | Confirmation for legal/financial actions | Test critical submissions |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] Errors are clearly described in text
|
||||
- [ ] Error messages explain how to fix
|
||||
- [ ] All inputs have visible labels
|
||||
- [ ] Required fields are indicated
|
||||
- [ ] Legal/financial submissions are reversible or confirmed
|
||||
|
||||
---
|
||||
|
||||
## Robust
|
||||
|
||||
Content must work with assistive technologies.
|
||||
|
||||
### 4.1 Compatible
|
||||
|
||||
| Criterion | Requirement | How to Test |
|
||||
|-----------|-------------|-------------|
|
||||
| 4.1.1 Parsing | Valid HTML | Run HTML validator |
|
||||
| 4.1.2 Name, Role, Value | ARIA used correctly | Inspect with accessibility tools |
|
||||
| 4.1.3 Status Messages (AA) | Status updates announced | Test with screen reader |
|
||||
|
||||
**Pass criteria:**
|
||||
- [ ] HTML is valid (no duplicate IDs, proper nesting)
|
||||
- [ ] Custom controls have appropriate roles
|
||||
- [ ] States (expanded, selected) are programmatically set
|
||||
- [ ] Status messages use aria-live regions
|
||||
|
||||
---
|
||||
|
||||
## Testing Tools
|
||||
|
||||
### Automated Testing
|
||||
|
||||
| Tool | What It Catches | Use For |
|
||||
|------|-----------------|---------|
|
||||
| axe DevTools | ~30% of issues | Quick scan |
|
||||
| WAVE | Similar to axe | Visual overlay |
|
||||
| Lighthouse | Basic accessibility | CI/CD integration |
|
||||
| Pa11y | CLI-based | Automated testing |
|
||||
|
||||
### Manual Testing Required
|
||||
|
||||
| Area | How to Test |
|
||||
|------|-------------|
|
||||
| Keyboard navigation | Unplug mouse, use only keyboard |
|
||||
| Screen reader | VoiceOver (Mac), NVDA (Windows) |
|
||||
| Zoom | Browser zoom to 200%, 400% |
|
||||
| Color contrast | WebAIM Contrast Checker |
|
||||
| Color blindness | Sim Daltonism, Chrome DevTools |
|
||||
|
||||
### Screen Reader Testing
|
||||
|
||||
**VoiceOver (Mac):**
|
||||
1. Enable: Cmd + F5
|
||||
2. Navigate: VO (Ctrl + Opt) + arrows
|
||||
3. Rotor: VO + U (headings, links, forms)
|
||||
|
||||
**NVDA (Windows):**
|
||||
1. Download free from nvaccess.org
|
||||
2. Navigate: Arrow keys, Tab
|
||||
3. Elements list: NVDA + F7
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: Common Failures
|
||||
|
||||
| Issue | Failure | Fix |
|
||||
|-------|---------|-----|
|
||||
| Missing alt text | 1.1.1 | Add descriptive alt or `alt=""` |
|
||||
| Low contrast | 1.4.3 | Increase to 4.5:1 (text) or 3:1 (large/UI) |
|
||||
| No focus indicator | 2.4.7 | Add visible :focus-visible styles |
|
||||
| Keyboard inaccessible | 2.1.1 | Make interactive, add tabindex=0 |
|
||||
| Missing form labels | 1.3.1, 3.3.2 | Add `<label>` with `for` attribute |
|
||||
| No skip link | 2.4.1 | Add "Skip to content" link |
|
||||
| Missing page title | 2.4.2 | Add descriptive `<title>` |
|
||||
| Color-only meaning | 1.4.1 | Add icon or text alongside |
|
||||
| No lang attribute | 3.1.1 | Add `<html lang="en">` |
|
||||
| Ambiguous links | 2.4.4 | Use descriptive link text |
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist Summary
|
||||
|
||||
Before launch, verify:
|
||||
|
||||
**Automated scan:**
|
||||
- [ ] axe DevTools shows 0 issues
|
||||
- [ ] Lighthouse accessibility score >90
|
||||
|
||||
**Keyboard:**
|
||||
- [ ] All functionality works with keyboard
|
||||
- [ ] Focus visible on all elements
|
||||
- [ ] No keyboard traps
|
||||
- [ ] Skip link works
|
||||
|
||||
**Screen reader:**
|
||||
- [ ] All content announced correctly
|
||||
- [ ] Form fields have labels
|
||||
- [ ] Images have alt text
|
||||
- [ ] Headings create logical outline
|
||||
|
||||
**Visual:**
|
||||
- [ ] All text meets contrast requirements
|
||||
- [ ] Works at 200% zoom
|
||||
- [ ] Works at 320px width (mobile)
|
||||
- [ ] Color not sole indicator
|
||||
@@ -0,0 +1,301 @@
|
||||
---
|
||||
name: ux-heuristics
|
||||
description: 'Evaluate and improve interface usability using heuristic analysis. Use when the user mentions "usability audit", "UX review", "users are confused", "heuristic evaluation", "form usability", or "navigation problems". Covers Nielsen''s 10 heuristics, severity ratings, and information architecture. For visual design fixes, see refactoring-ui. For conversion-focused audits, see cro-methodology.'
|
||||
license: MIT
|
||||
metadata:
|
||||
author: wondelai
|
||||
version: "1.2.1"
|
||||
---
|
||||
|
||||
# UX Heuristics Framework
|
||||
|
||||
Practical usability principles for evaluating and improving user interfaces. Based on a fundamental truth: users don't read, they scan. They don't make optimal choices, they satisfice. They don't figure out how things work, they muddle through.
|
||||
|
||||
## Core Principle
|
||||
|
||||
**"Don't Make Me Think"** - Every page should be self-evident. If something requires thinking, it's a usability problem.
|
||||
|
||||
**The foundation:** Users have limited patience and cognitive bandwidth. The best interfaces are invisible -- they let users accomplish goals without ever stopping to wonder "What do I click?" or "Where am I?" Every question mark that pops into a user's head adds to cognitive load and increases the chance they'll leave. Design for scanning, satisficing, and muddling through -- because that's what users actually do.
|
||||
|
||||
## Scoring
|
||||
|
||||
**Goal: 10/10.** When reviewing or creating user interfaces, rate them 0-10 based on adherence to the principles below. A 10/10 means full alignment with all guidelines; lower scores indicate gaps to address. Always provide the current score and specific improvements needed to reach 10/10.
|
||||
|
||||
## Krug's Three Laws of Usability
|
||||
|
||||
### 1. Don't Make Me Think
|
||||
|
||||
**Core concept:** Every question mark that pops into a user's head adds to their cognitive load and distracts from the task.
|
||||
|
||||
**Why it works:** Users are on a mission. They don't want to puzzle over labels, wonder what a link does, or decode clever marketing language. The less thinking required, the more likely they complete the task.
|
||||
|
||||
**Key insights:**
|
||||
- Clever names lose to clear names every time
|
||||
- Marketing-speak creates friction; plain language removes it
|
||||
- Unfamiliar categories and labels force users to stop and interpret
|
||||
- Links that could go anywhere create uncertainty
|
||||
- Buttons with ambiguous labels cause hesitation
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Application | Example |
|
||||
|---------|-------------|---------|
|
||||
| **Navigation labels** | Use self-evident names | "Get directions" not "Calculate route to destination" |
|
||||
| **CTAs** | Use action verbs users understand | "Sign in" not "Access your account portal" |
|
||||
| **E-commerce** | Match user mental models | "Add to cart" not "Proceed to purchase selection" |
|
||||
| **Form labels** | Describe what's needed plainly | "Email address" not "Electronic correspondence identifier" |
|
||||
| **Error states** | Tell users what to do next | "Check your email format" not "Validation error" |
|
||||
|
||||
**Copy patterns:**
|
||||
- Self-evident labels: "Sign in", "Search", "Add to cart"
|
||||
- Action-oriented buttons: verb + noun ("Create account", "Download report")
|
||||
- Avoid jargon: "Save" not "Persist", "Remove" not "Disassociate"
|
||||
- If a label needs explanation, simplify the label
|
||||
|
||||
**Ethical boundary:** Clarity should serve users, not obscure information. Never use plain language as a veneer to hide unfavorable terms.
|
||||
|
||||
See: [references/krug-principles.md](references/krug-principles.md) for full Krug methodology.
|
||||
|
||||
### 2. It Doesn't Matter How Many Clicks
|
||||
|
||||
**Core concept:** The myth says "users leave after 3 clicks." The reality is users don't mind clicks if each one is painless, obvious, and confidence-building.
|
||||
|
||||
**Why it works:** Cognitive effort per click matters more than click count. Three mindless, confident clicks are far better than one click that requires deliberation. Users abandon when they lose confidence, not when they run out of patience for clicking.
|
||||
|
||||
**Key insights:**
|
||||
- Each click should be painless (fast, easy)
|
||||
- Each click should be obvious (no thinking required)
|
||||
- Each click should build confidence (users know they're on the right path)
|
||||
- Three mindless clicks beat one confusing click every time
|
||||
- Users abandon when confused, not when they've clicked too many times
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Application | Example |
|
||||
|---------|-------------|---------|
|
||||
| **Information architecture** | Prioritize clarity over depth | Shallow nav with clear labels over deep nav with vague ones |
|
||||
| **Checkout flows** | Make each step obvious | Clear step indicators with descriptive labels |
|
||||
| **Settings** | Organize into clear categories | "Account > Security > Change password" (3 confident clicks) |
|
||||
| **Search results** | Let users drill down confidently | Category filters that narrow results progressively |
|
||||
| **Onboarding** | Guide with small, clear steps | Wizard with one clear action per step |
|
||||
|
||||
**Copy patterns:**
|
||||
- Progress indicators: "Step 2 of 4: Shipping details"
|
||||
- Breadcrumbs: "Home > Products > Shoes > Running"
|
||||
- Confirmations at each step: "Great, your email is verified. Now let's set up your profile."
|
||||
- Clear link text: "View all running shoes" not "Click here"
|
||||
|
||||
**Ethical boundary:** Don't use extra steps to bury cancellation flows or make opting out harder. Every click should move users toward their goal, not away from it.
|
||||
|
||||
See: [references/krug-principles.md](references/krug-principles.md) for Krug's click philosophy and scanning behavior.
|
||||
|
||||
### 3. Get Rid of Half the Words
|
||||
|
||||
**Core concept:** Get rid of half the words on each page, then get rid of half of what's left. Brevity reduces noise, makes useful content more prominent, and shows respect for the user's time.
|
||||
|
||||
**Why it works:** Users scan -- they don't read. Every unnecessary word competes with the words that matter. Removing fluff makes important content more discoverable and pages shorter.
|
||||
|
||||
**Key insights:**
|
||||
- Happy-talk ("Welcome to our website!") wastes space
|
||||
- Instructions nobody reads should be removed
|
||||
- "Please" and "Kindly" and polite fluff add noise
|
||||
- Redundant explanations dilute the message
|
||||
- Shorter pages mean less scrolling and faster scanning
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Application | Example |
|
||||
|---------|-------------|---------|
|
||||
| **Landing pages** | Cut welcome copy, lead with value | Remove "Welcome to..." paragraphs |
|
||||
| **Error messages** | State problem and fix, nothing more | "Password too short (min 8 chars)" not a paragraph |
|
||||
| **Tooltips** | One sentence max | "Last 4 digits of your card" not a full explanation |
|
||||
| **Empty states** | Action-oriented, minimal | "No results. Try a different search." |
|
||||
| **Onboarding** | One instruction per screen | "Choose your interests" not a wall of explanatory text |
|
||||
|
||||
**Copy patterns:**
|
||||
- Before: "Please kindly note that you will need to enter your password in order to proceed to the next step."
|
||||
- After: "Enter your password to continue."
|
||||
- Before: "We've received your message and will get back to you as soon as possible."
|
||||
- After: "Message sent. We'll reply within 24 hours."
|
||||
|
||||
**Ethical boundary:** Brevity must not mean omitting critical information. Concise disclosures for pricing, terms, and data usage are a user right.
|
||||
|
||||
See: [references/krug-principles.md](references/krug-principles.md) for Krug's word-cutting methodology.
|
||||
|
||||
### 4. The Trunk Test
|
||||
|
||||
**Core concept:** A test for navigation clarity: if users were dropped on any random page (like being locked in a car trunk and released at a random spot), could they instantly answer six key questions?
|
||||
|
||||
**Why it works:** Good navigation gives users constant orientation. If users can't identify where they are and what their options are, they feel lost and leave.
|
||||
|
||||
**Key insights:**
|
||||
- Users must know what site they're on (brand/logo visible)
|
||||
- Users must know what page they're on (clear heading)
|
||||
- Major sections must be visible (navigation)
|
||||
- Options at this level must be clear (links/buttons)
|
||||
- Position in hierarchy must be apparent (breadcrumbs)
|
||||
- Search must be findable
|
||||
|
||||
**Product applications:**
|
||||
|
||||
| Context | Application | Example |
|
||||
|---------|-------------|---------|
|
||||
| **Global nav** | Persistent site ID and sections | Logo top-left, main nav always visible |
|
||||
| **Page headers** | Clear, descriptive page titles | "Running Shoes - Men's" not just "Products" |
|
||||
| **Breadcrumbs** | Show hierarchy on all inner pages | "Home > Products > Shoes > Running" |
|
||||
| **Mobile nav** | Maintain orientation in hamburger menus | Highlight current section, show breadcrumbs |
|
||||
| **Search** | Visible search on every page | Search box in header, not buried in footer |
|
||||
|
||||
**Copy patterns:**
|
||||
- Page titles that match the link the user clicked
|
||||
- "You are here" indicators (highlighted nav items, bold breadcrumb)
|
||||
- Section headings that orient: "Your Account > Billing" not just "Settings"
|
||||
- Footer navigation for secondary discovery
|
||||
|
||||
**Ethical boundary:** Navigation should honestly represent site structure. Don't use misleading labels to funnel users into marketing pages.
|
||||
|
||||
See: [references/krug-principles.md](references/krug-principles.md) for the full Trunk Test methodology.
|
||||
|
||||
## Nielsen's 10 Usability Heuristics
|
||||
|
||||
### 1. Visibility of System Status
|
||||
Keep users informed about what's happening through timely feedback. Every action needs acknowledgment — progress bars for uploads, confirmations for submissions, skeleton screens for loading. Silent failures destroy trust. Copy pattern: "Saving..." → "Saved" (immediate state transitions).
|
||||
|
||||
### 2. Match Between System and Real World
|
||||
Speak users' language, not system language. Use "Sign in" not "Authenticate", "Search" not "Query." Follow real-world metaphors (trash bin, shopping cart) and natural ordering (street → city → state → zip). One term per concept, everywhere.
|
||||
|
||||
### 3. User Control and Freedom
|
||||
Provide clear "emergency exits." Undo beats "Are you sure?" dialogs every time — users click through confirmations without reading. Every flow needs cancel/exit, back buttons must never break, and soft delete with undo beats permanent deletion.
|
||||
|
||||
### 4. Consistency and Standards
|
||||
Same words, styles, and behaviors should mean the same thing throughout. Internal consistency (your app) and external consistency (platform conventions: logo top-left, search top-right). Pick one term per concept — "Projects" everywhere, never mixing with "Workspaces."
|
||||
|
||||
### 5. Error Prevention
|
||||
Prevent problems before they occur. Constrained inputs (date pickers over text fields), autocomplete, sensible defaults, and "unsaved changes" warnings. Two error types need different prevention: slips (accidental wrong action) and mistakes (wrong intention).
|
||||
|
||||
### 6. Recognition Rather Than Recall
|
||||
Minimize memory load — show options, don't require memorization. Breadcrumbs, recent searches, pre-filled fields, dropdowns with decoded values (country names, not codes). Human working memory holds ~7 items; recognition is far easier than recall.
|
||||
|
||||
### 7. Flexibility and Efficiency of Use
|
||||
Serve both novices and experts. Keyboard shortcuts, touch gestures, bulk actions, saved searches, and command palettes (Cmd+K) speed up power users. Progressive disclosure keeps it simple for beginners while experts access full power.
|
||||
|
||||
### 8. Aesthetic and Minimalist Design
|
||||
Every element must earn its place. Signal-to-noise ratio determines usability — when everything screams for attention, nothing stands out. Show what matters now, hide what doesn't. One primary CTA per page, not five competing ones.
|
||||
|
||||
### 9. Help Users Recognize, Diagnose, and Recover from Errors
|
||||
Error messages need three parts: what happened, why, and how to fix it. Plain language always ("Connection failed" not "ECONNREFUSED"), specific ("Password must be 8+ characters" not "Invalid"), never blame the user, and preserve their input.
|
||||
|
||||
### 10. Help and Documentation
|
||||
Help should be searchable, task-focused ("How to..." not technical reference), and contextual (tooltips, inline hints). Types: inline help, contextual "?" icons, searchable knowledge base, guided tours, live support.
|
||||
|
||||
See: [references/nielsen-heuristics.md](references/nielsen-heuristics.md) for detailed examples, product applications, copy patterns, and ethical boundaries for all 10 heuristics.
|
||||
|
||||
## Severity Rating Scale
|
||||
|
||||
When auditing interfaces, rate each issue:
|
||||
|
||||
| Severity | Rating | Description | Priority |
|
||||
|----------|--------|-------------|----------|
|
||||
| **0** | Not a problem | Disagreement, not usability issue | Ignore |
|
||||
| **1** | Cosmetic | Minor annoyance, low impact | Fix if time |
|
||||
| **2** | Minor | Causes delay or frustration | Schedule fix |
|
||||
| **3** | Major | Significant task failure | Fix soon |
|
||||
| **4** | Catastrophic | Prevents task completion | Fix immediately |
|
||||
|
||||
### Rating Factors
|
||||
|
||||
Consider all three:
|
||||
|
||||
1. **Frequency:** How often does it occur?
|
||||
2. **Impact:** How severe when it occurs?
|
||||
3. **Persistence:** One-time or ongoing problem?
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
| Mistake | Why It Fails | Fix |
|
||||
|---------|-------------|------|
|
||||
| **Mystery meat navigation** | Icons without labels force guessing | Add text labels alongside icons |
|
||||
| **Too many choices** | Decision paralysis slows users | Reduce to 7 plus/minus 2 items |
|
||||
| **No "you are here" indicator** | Users feel lost in the hierarchy | Highlight current section in nav and breadcrumbs |
|
||||
| **No inline validation** | Submit, error, scroll cycle frustrates | Validate on blur with specific messages |
|
||||
| **Unclear required fields** | Users confused about what's mandatory | Mark optional fields, not required (most fields should be required) |
|
||||
| **Wall of text** | Nobody reads dense paragraphs | Break up with headings, bullets, whitespace |
|
||||
| **Jargon in labels** | Users don't speak your internal language | User-test all labels, use plain language |
|
||||
| **No loading indicators** | Users think the system is broken | Show spinner, progress bar, or skeleton screen |
|
||||
| **Tiny tap targets** | Mobile users misclick constantly | Minimum 44x44 px touch targets |
|
||||
| **Hover-only information** | Mobile and keyboard users miss it entirely | Don't hide critical info behind hover states |
|
||||
| **No undo** | Users afraid to take any action | Provide undo for all non-destructive actions |
|
||||
| **Poor error messages** | "Invalid input" tells users nothing | Explain what's wrong and how to fix it |
|
||||
| **Low contrast text** | Unreadable for many users | WCAG AA minimum (4.5:1 contrast ratio) |
|
||||
| **Inconsistent nav location** | Users can't find navigation | Fixed position, same location on every page |
|
||||
| **Broken back button** | Fundamental browser contract violated | Never hijack or break browser history |
|
||||
|
||||
## Quick Diagnostic
|
||||
|
||||
Audit any interface:
|
||||
|
||||
| Question | If No | Action |
|
||||
|----------|-------|--------|
|
||||
| Can I tell what site/page this is immediately? | Users are lost and disoriented | Add clear logo, page title, and breadcrumbs |
|
||||
| Is the main action obvious? | Users don't know what to do | Create visual hierarchy, single primary CTA |
|
||||
| Is the navigation clear? | Users can't find their way | Apply the Trunk Test, add "you are here" indicators |
|
||||
| Can I find the search? | Users with specific goals are blocked | Add visible search box in header |
|
||||
| Does the system show me what's happening? | Users lose trust and re-click | Add loading states, confirmations, progress indicators |
|
||||
| Are error messages helpful? | Users get stuck on errors | Rewrite in plain language with specific fix |
|
||||
| Can users undo or go back? | Users are afraid to act | Add undo, cancel, and back options everywhere |
|
||||
| Does it work without hover? | Mobile and keyboard users are excluded | Replace hover-only interactions with visible alternatives |
|
||||
| Are all interactive elements labeled? | Users guess at icon meanings | Add text labels or descriptive tooltips |
|
||||
| Does anything make me stop and think "huh?" | Cognitive load is too high | Simplify -- if it needs explanation, redesign it |
|
||||
|
||||
## Heuristic Conflicts
|
||||
|
||||
Heuristics sometimes contradict each other. When they do:
|
||||
- **Simplicity vs. Flexibility**: Use progressive disclosure
|
||||
- **Consistency vs. Context**: Consistent patterns, contextual prominence
|
||||
- **Efficiency vs. Error Prevention**: Prefer undo over confirmation dialogs
|
||||
- **Discoverability vs. Minimalism**: Primary actions visible, secondary hidden
|
||||
|
||||
See: [references/heuristic-conflicts.md](references/heuristic-conflicts.md) for resolution frameworks.
|
||||
|
||||
## Dark Patterns Recognition
|
||||
|
||||
Dark patterns violate heuristics deliberately to manipulate users:
|
||||
- Forced continuity (hard to cancel)
|
||||
- Roach motel (easy in, hard out)
|
||||
- Confirmshaming (guilt-based options)
|
||||
- Hidden costs (surprise fees at checkout)
|
||||
|
||||
See: [references/dark-patterns.md](references/dark-patterns.md) for complete taxonomy and ethical alternatives.
|
||||
|
||||
## When to Use Each Method
|
||||
|
||||
| Method | When | Time | Findings |
|
||||
|--------|------|------|----------|
|
||||
| Heuristic evaluation | Before user testing | 1-2 hours | Major violations |
|
||||
| User testing | After heuristic fixes | 2-4 hours | Real behavior |
|
||||
| A/B testing | When optimizing | Days-weeks | Statistical validation |
|
||||
| Analytics review | Ongoing | 30 min | Patterns and problems |
|
||||
|
||||
## Reference Files
|
||||
|
||||
- [krug-principles.md](references/krug-principles.md): Full Krug methodology, scanning behavior, navigation clarity
|
||||
- [nielsen-heuristics.md](references/nielsen-heuristics.md): Detailed heuristic explanations with examples
|
||||
- [audit-template.md](references/audit-template.md): Structured heuristic evaluation template
|
||||
- [dark-patterns.md](references/dark-patterns.md): Categories, examples, ethical alternatives, regulations
|
||||
- [wcag-checklist.md](references/wcag-checklist.md): Complete WCAG 2.1 AA checklist, testing tools
|
||||
- [cultural-ux.md](references/cultural-ux.md): RTL, color meanings, form conventions, localization
|
||||
- [heuristic-conflicts.md](references/heuristic-conflicts.md): When heuristics contradict, resolution frameworks
|
||||
|
||||
## Further Reading
|
||||
|
||||
This skill is based on usability principles developed by Steve Krug and Jakob Nielsen:
|
||||
|
||||
- [*"Don't Make Me Think, Revisited"*](https://www.amazon.com/Dont-Make-Think-Revisited-Usability/dp/0321965515?tag=wondelai00-20) by Steve Krug
|
||||
- [*"Rocket Surgery Made Easy"*](https://www.amazon.com/Rocket-Surgery-Made-Easy-Yourself/dp/0321657292?tag=wondelai00-20) by Steve Krug (DIY usability testing)
|
||||
- [*"10 Usability Heuristics for User Interface Design"*](https://www.nngroup.com/articles/ten-usability-heuristics/) by Jakob Nielsen (Nielsen Norman Group)
|
||||
|
||||
## About the Author
|
||||
|
||||
**Steve Krug** is a usability consultant who has been helping companies make their products more intuitive since the 1990s. His book *"Don't Make Me Think"* (first published in 2000, revised 2014) is the most widely read book on web usability and is considered essential reading for anyone involved in designing interfaces. Known for his accessible, humorous writing style and his advocacy for low-cost usability testing, Krug demonstrated that usability doesn't require a lab or a large budget -- just watching a few real users try to accomplish tasks.
|
||||
|
||||
**Jakob Nielsen, PhD** is co-founder of the Nielsen Norman Group (NN/g) and is widely regarded as the "king of usability." His 10 Usability Heuristics for User Interface Design, published in 1994, remain the most-used framework for heuristic evaluation worldwide. Nielsen has been called "the guru of Web page usability" by *The New York Times* and has authored numerous influential books on usability engineering. His research-driven approach to interface design helped establish usability as a recognized discipline in software development.
|
||||
226
skills/commissioning-skill/hook-patterns.md
Normal file
226
skills/commissioning-skill/hook-patterns.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# Hook patterns
|
||||
|
||||
Hooks are compiled from spore decisions where the policy is fully deterministic.
|
||||
They fire via the cchooks runtime before the scion reasons, at zero token cost.
|
||||
|
||||
Install cchooks: `pip install cchooks` or `uv add cchooks`
|
||||
|
||||
---
|
||||
|
||||
## When to generate a hook vs. keep as spore-only
|
||||
|
||||
Generate a hook when:
|
||||
- The trigger condition is pattern-matchable without contextual judgment
|
||||
- The outcome is always the same given the trigger (no "it depends")
|
||||
- The decision can be encoded in ~20 lines of Python
|
||||
|
||||
Keep as spore-only when:
|
||||
- The decision requires the scion to interpret context before acting
|
||||
- The outcome varies based on factors not visible in the tool call metadata
|
||||
- The policy involves weighing tradeoffs
|
||||
|
||||
A spore entry should always exist for every hook (provenance). Not every spore
|
||||
entry needs a hook (judgment).
|
||||
|
||||
---
|
||||
|
||||
## File write guard
|
||||
|
||||
The most common hook type for annotator and transformer task classes. Blocks
|
||||
writes to sensitive paths before the scion acts.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# hooks/env-guard.py
|
||||
# Spore: env-file-write-guard
|
||||
# Blocks writes to credential and sensitive config files.
|
||||
|
||||
from cchooks import create_context, PreToolUseContext
|
||||
|
||||
SENSITIVE_PATTERNS = {".env", "secrets.json", "id_rsa", ".pem", ".key"}
|
||||
|
||||
c = create_context()
|
||||
assert isinstance(c, PreToolUseContext)
|
||||
|
||||
if c.tool_name == "Write":
|
||||
file_path = c.tool_input.get("file_path", "")
|
||||
if any(pattern in file_path for pattern in SENSITIVE_PATTERNS):
|
||||
c.output.deny(
|
||||
reason=f"Credential file protected: {file_path}",
|
||||
system_message="Sensitive file write blocked by spore policy. Escalate to parent if this write is intentional."
|
||||
)
|
||||
else:
|
||||
c.output.allow()
|
||||
else:
|
||||
c.output.allow()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bash command guard
|
||||
|
||||
Blocks destructive or elevated-privilege bash patterns. Appropriate for any
|
||||
task class that does not require shell execution.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# hooks/bash-guard.py
|
||||
# Spore: destructive-bash-guard
|
||||
# Blocks bash commands matching destructive or privilege-escalation patterns.
|
||||
|
||||
from cchooks import create_context, PreToolUseContext
|
||||
|
||||
BLOCKED_PATTERNS = ["rm -rf", "sudo", "fdisk", "format", "dd if=", "mkfs"]
|
||||
|
||||
c = create_context()
|
||||
assert isinstance(c, PreToolUseContext)
|
||||
|
||||
if c.tool_name == "Bash":
|
||||
command = c.tool_input.get("command", "")
|
||||
for pattern in BLOCKED_PATTERNS:
|
||||
if pattern in command:
|
||||
c.output.deny(
|
||||
reason=f"Blocked pattern in bash command: {pattern}",
|
||||
system_message="Destructive command blocked by spore policy. Escalate to parent if this command is required."
|
||||
)
|
||||
break
|
||||
else:
|
||||
c.output.allow()
|
||||
else:
|
||||
c.output.allow()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Output format enforcer
|
||||
|
||||
Fires after a write and validates that output conforms to the required format.
|
||||
Appropriate for annotator task classes with strict output contracts.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# hooks/output-format-validator.py
|
||||
# Spore: annotation-output-format
|
||||
# Validates that annotation output files are valid JSONL.
|
||||
|
||||
import json
|
||||
from cchooks import create_context, PostToolUseContext
|
||||
|
||||
c = create_context()
|
||||
assert isinstance(c, PostToolUseContext)
|
||||
|
||||
if c.tool_name == "Write" and c.tool_input.get("file_path", "").endswith(".annotation"):
|
||||
content = c.tool_input.get("content", "")
|
||||
lines = [l.strip() for l in content.strip().splitlines() if l.strip()]
|
||||
invalid_lines = []
|
||||
for i, line in enumerate(lines):
|
||||
try:
|
||||
json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
invalid_lines.append(i + 1)
|
||||
if invalid_lines:
|
||||
c.output.challenge(
|
||||
reason=f"Output format violation: lines {invalid_lines} are not valid JSON",
|
||||
system_message="Annotation output must be JSONL (one JSON object per line). Correct and retry."
|
||||
)
|
||||
else:
|
||||
c.output.accept()
|
||||
else:
|
||||
c.output.accept()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Session start context loader
|
||||
|
||||
Fires when the scion session starts and loads the spore file path into context.
|
||||
Use this to ensure the scion always reads its spore file on startup.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# hooks/session-start.py
|
||||
# Loads spore file contents into session context on startup.
|
||||
|
||||
import os
|
||||
from cchooks import create_context, SessionStartContext
|
||||
|
||||
SPORE_FILE = os.environ.get("SCION_SPORE_FILE", "")
|
||||
|
||||
c = create_context()
|
||||
assert isinstance(c, SessionStartContext)
|
||||
|
||||
if c.source == "startup" and SPORE_FILE and os.path.exists(SPORE_FILE):
|
||||
with open(SPORE_FILE) as f:
|
||||
content = f.read()
|
||||
print(f"Spore policy loaded from {SPORE_FILE}:\n\n{content}")
|
||||
|
||||
c.output.exit_success()
|
||||
```
|
||||
|
||||
Set `SCION_SPORE_FILE` in the scion's environment at spawn time. The commissioning
|
||||
skill is responsible for setting this variable to the correct spore file path.
|
||||
|
||||
---
|
||||
|
||||
## Hook registration in claude settings
|
||||
|
||||
Register hooks in `.claude/settings.json` at the project or user level:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "python hooks/env-guard.py" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "python hooks/bash-guard.py" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "python hooks/output-format-validator.py" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "python hooks/session-start.py" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation before registration
|
||||
|
||||
Every hook must be validated before the commissioning skill registers it.
|
||||
Minimal validation: run the hook with a synthetic input that should trigger
|
||||
the deny/challenge condition and confirm it fires correctly.
|
||||
|
||||
```bash
|
||||
# Test env-guard with a synthetic Write event targeting .env
|
||||
echo '{"tool_name": "Write", "tool_input": {"file_path": ".env", "content": "SECRET=x"}}' \
|
||||
| python hooks/env-guard.py
|
||||
# Expected: exit code 2 (deny) with reason message
|
||||
|
||||
# Test env-guard with a safe path — should allow
|
||||
echo '{"tool_name": "Write", "tool_input": {"file_path": "output.txt", "content": "hello"}}' \
|
||||
| python hooks/env-guard.py
|
||||
# Expected: exit code 0 (allow)
|
||||
```
|
||||
|
||||
If a hook does not pass both cases, do not register it and do not reference it
|
||||
in the spore entry. Fix or remove before depositing.
|
||||
366
skills/commissioning-skill/hook-protocol.md
Normal file
366
skills/commissioning-skill/hook-protocol.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# Hook protocol
|
||||
|
||||
Claude Code hooks are executables that intercept agent lifecycle events. They
|
||||
require no SDK or external library — the protocol is just stdin JSON in,
|
||||
exit code out. This file contains everything needed to generate correct hooks
|
||||
in Python (stdlib only) or JavaScript (Node.js, no packages).
|
||||
|
||||
---
|
||||
|
||||
## The protocol
|
||||
|
||||
Claude Code spawns the hook executable, writes a JSON event to its stdin, and
|
||||
reads the exit code and stdout when it exits.
|
||||
|
||||
**Exit codes:**
|
||||
|
||||
- `0` — allow / success. Claude Code continues. Any stdout is added to context.
|
||||
- `1` — non-blocking error. Claude Code sees the message and continues anyway.
|
||||
- `2` — blocking deny. Claude Code stops the action and shows the reason.
|
||||
|
||||
**JSON control output** (optional, write to stdout as valid JSON):
|
||||
|
||||
For `PreToolUse` hooks, you can output a JSON object instead of relying solely
|
||||
on the exit code. This gives you a reason string that Claude Code surfaces to
|
||||
the agent:
|
||||
|
||||
```json
|
||||
{"decision": "block", "reason": "Credential file write blocked by spore policy"}
|
||||
```
|
||||
|
||||
```json
|
||||
{"decision": "approve"}
|
||||
```
|
||||
|
||||
For `PostToolUse` hooks, stdout text (not JSON) is injected into the agent's
|
||||
context as tool result feedback.
|
||||
|
||||
**Input event shape** (arrives on stdin as a single JSON line):
|
||||
|
||||
```json
|
||||
{
|
||||
"tool_name": "Write",
|
||||
"tool_input": {
|
||||
"file_path": "path/to/file",
|
||||
"content": "file content"
|
||||
},
|
||||
"tool_response": "...",
|
||||
"session_id": "abc123"
|
||||
}
|
||||
```
|
||||
|
||||
`tool_response` is only present on `PostToolUse` events.
|
||||
`tool_input` shape varies by tool — see field names below.
|
||||
|
||||
**Common tool_input fields by tool:**
|
||||
|
||||
- `Write` / `Edit` / `MultiEdit`: `file_path`, `content`
|
||||
- `Bash`: `command`
|
||||
- `Read`: `file_path`
|
||||
- `WebFetch`: `url`
|
||||
|
||||
---
|
||||
|
||||
## Python template (stdlib only)
|
||||
|
||||
Use this for projects that have Python available. Zero dependencies — no pip
|
||||
install, no virtualenv. Copy, rename, fill in the guard logic.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# hooks/{hook-name}.py
|
||||
# Spore: {spore-identifier}
|
||||
# {one-line description of what this hook guards}
|
||||
#
|
||||
# Protocol: reads JSON event from stdin, exits 0 (allow) or 2 (deny).
|
||||
# No external dependencies — stdlib only.
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# Read the full event from stdin
|
||||
try:
|
||||
event = json.loads(sys.stdin.read())
|
||||
except (json.JSONDecodeError, OSError):
|
||||
# If we can't read the event, allow and let the agent handle it
|
||||
sys.exit(0)
|
||||
|
||||
tool_name = event.get("tool_name", "")
|
||||
tool_input = event.get("tool_input", {})
|
||||
|
||||
# --- Guard logic starts here ---
|
||||
# Replace this block with the compiled spore decision.
|
||||
# Example: block writes to credential files.
|
||||
if tool_name == "Write":
|
||||
file_path = tool_input.get("file_path", "")
|
||||
BLOCKED = {".env", "secrets.json", "id_rsa", ".pem", ".key"}
|
||||
if any(pattern in file_path for pattern in BLOCKED):
|
||||
# Write structured denial to stdout for Claude Code to surface
|
||||
print(json.dumps({
|
||||
"decision": "block",
|
||||
"reason": f"Credential file protected by spore policy: {file_path}"
|
||||
}))
|
||||
sys.exit(2)
|
||||
# --- Guard logic ends here ---
|
||||
|
||||
# Default: allow
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JavaScript template (Node.js, no packages)
|
||||
|
||||
Use this for TypeScript/JavaScript projects, or any project where Node.js is
|
||||
available (Claude Code itself runs on Node so it is always present). Zero
|
||||
dependencies — pure Node.js stdlib.
|
||||
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
// hooks/{hook-name}.js
|
||||
// Spore: {spore-identifier}
|
||||
// {one-line description of what this hook guards}
|
||||
//
|
||||
// Protocol: reads JSON event from stdin, exits 0 (allow) or 2 (deny).
|
||||
// No external dependencies — Node.js stdlib only.
|
||||
|
||||
async function main() {
|
||||
// Read the full event from stdin
|
||||
let raw = "";
|
||||
for await (const chunk of process.stdin) {
|
||||
raw += chunk;
|
||||
}
|
||||
|
||||
let event;
|
||||
try {
|
||||
event = JSON.parse(raw);
|
||||
} catch {
|
||||
// If we can't parse the event, allow and let the agent handle it
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const toolName = event.tool_name ?? "";
|
||||
const toolInput = event.tool_input ?? {};
|
||||
|
||||
// --- Guard logic starts here ---
|
||||
// Replace this block with the compiled spore decision.
|
||||
// Example: block writes to credential files.
|
||||
if (toolName === "Write") {
|
||||
const filePath = toolInput.file_path ?? "";
|
||||
const blocked = [".env", "secrets.json", "id_rsa", ".pem", ".key"];
|
||||
if (blocked.some((pattern) => filePath.includes(pattern))) {
|
||||
// Write structured denial to stdout for Claude Code to surface
|
||||
process.stdout.write(JSON.stringify({
|
||||
decision: "block",
|
||||
reason: `Credential file protected by spore policy: ${filePath}`,
|
||||
}));
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
// --- Guard logic ends here ---
|
||||
|
||||
// Default: allow
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch(() => process.exit(0)); // On unexpected error, fail open
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PostToolUse validator template (Python)
|
||||
|
||||
PostToolUse hooks receive the completed tool result and can inject feedback
|
||||
into the agent's context. Use for output format validation, quality checks,
|
||||
or logging.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# hooks/{hook-name}-post.py
|
||||
# Spore: {spore-identifier}
|
||||
# Validates output after a tool completes. Injects feedback into agent context.
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
event = json.loads(sys.stdin.read())
|
||||
except (json.JSONDecodeError, OSError):
|
||||
sys.exit(0)
|
||||
|
||||
tool_name = event.get("tool_name", "")
|
||||
tool_input = event.get("tool_input", {})
|
||||
# tool_response contains what the tool returned
|
||||
tool_response = event.get("tool_response", "")
|
||||
|
||||
# --- Validation logic starts here ---
|
||||
# Example: verify annotation output is valid JSONL
|
||||
if tool_name == "Write":
|
||||
file_path = tool_input.get("file_path", "")
|
||||
if file_path.endswith(".annotation"):
|
||||
content = tool_input.get("content", "")
|
||||
invalid = []
|
||||
for i, line in enumerate(content.strip().splitlines(), 1):
|
||||
if line.strip():
|
||||
try:
|
||||
json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
invalid.append(i)
|
||||
if invalid:
|
||||
# Exit 2 to block; message shown to agent
|
||||
print(json.dumps({
|
||||
"decision": "block",
|
||||
"reason": f"Output format violation: lines {invalid} are not valid JSON. "
|
||||
f"Annotation output must be JSONL (one JSON object per line)."
|
||||
}))
|
||||
sys.exit(2)
|
||||
# --- Validation logic ends here ---
|
||||
|
||||
# Exit 0; any stdout text gets injected into agent context as feedback
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SessionStart context loader template (Python)
|
||||
|
||||
Loads the spore file into session context on startup so the scion has its
|
||||
policy available from the first turn.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# hooks/session-start.py
|
||||
# Loads SCION_SPORE_FILE contents into session context on startup.
|
||||
# The commissioning skill sets SCION_SPORE_FILE at spawn time.
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
event = json.loads(sys.stdin.read())
|
||||
except (json.JSONDecodeError, OSError):
|
||||
sys.exit(0)
|
||||
|
||||
# Only act on startup, not resume or clear
|
||||
if event.get("source") != "startup":
|
||||
sys.exit(0)
|
||||
|
||||
spore_file = os.environ.get("SCION_SPORE_FILE", "")
|
||||
if not spore_file or not os.path.exists(spore_file):
|
||||
# No spore file configured — allow and continue
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
with open(spore_file) as f:
|
||||
content = f.read()
|
||||
# Print to stdout — SessionStart hook output is added to session context
|
||||
print(f"Spore policy active ({spore_file}):\n\n{content}")
|
||||
except OSError:
|
||||
pass # Fail open — don't block startup if spore file unreadable
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Registration in .claude/settings.json
|
||||
|
||||
Register hooks at the module level (`.claude/settings.json` in the module
|
||||
folder) rather than globally. This keeps hook scope aligned with spore scope.
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "python hooks/env-guard.py" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "python hooks/bash-guard.py" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "python hooks/output-validator-post.py" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "python hooks/session-start.py" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For Node.js hooks, replace `python hooks/name.py` with `node hooks/name.js`.
|
||||
For executable hooks (chmod +x), the command can be the file path directly.
|
||||
|
||||
---
|
||||
|
||||
## Choosing Python vs JavaScript
|
||||
|
||||
Use Python when the project uses Python or when no preference is stated —
|
||||
Python stdlib is universally available and the syntax reads clearly to most
|
||||
agents. Use JavaScript (Node.js) when the project is a TypeScript/JavaScript
|
||||
codebase, or when the hook needs to share logic with existing project tooling.
|
||||
The bartolli pattern (`.claude/hooks/{project-type}/quality-check.js`) is
|
||||
worth adopting when multiple hook types share configuration — see that repo
|
||||
for a mature example of project-type-aware Node.js hooks with SHA256 config
|
||||
caching for performance.
|
||||
|
||||
Either language produces identical protocol behavior. The hook protocol does
|
||||
not care what generates the exit code.
|
||||
|
||||
---
|
||||
|
||||
## Validation before registration
|
||||
|
||||
Every generated hook must pass two test cases before the commissioning skill
|
||||
registers it: one that should trigger the deny condition, and one that should
|
||||
allow cleanly. Run manually:
|
||||
|
||||
```bash
|
||||
# Should exit 2 (deny) — triggers the guard
|
||||
echo '{"tool_name":"Write","tool_input":{"file_path":".env","content":"SECRET=x"}}' \
|
||||
| python hooks/env-guard.py
|
||||
echo "Exit code: $?"
|
||||
|
||||
# Should exit 0 (allow) — safe path
|
||||
echo '{"tool_name":"Write","tool_input":{"file_path":"output.txt","content":"hello"}}' \
|
||||
| python hooks/env-guard.py
|
||||
echo "Exit code: $?"
|
||||
```
|
||||
|
||||
If either test fails, do not register the hook or reference it in a spore entry.
|
||||
Fix first, validate again, then deposit.
|
||||
163
skills/commissioning-skill/seeds-entry-format.md
Normal file
163
skills/commissioning-skill/seeds-entry-format.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# SEEDS.md entry format
|
||||
|
||||
SEEDS.md lives at the module root and serves two purposes simultaneously:
|
||||
a routing index for the commissioning skill, and a provenance log for the
|
||||
parent agent. Each entry corresponds to one spore file.
|
||||
|
||||
---
|
||||
|
||||
## File header
|
||||
|
||||
```markdown
|
||||
# SEEDS.md
|
||||
# module: {module-name}
|
||||
# maintained-by: parent agent
|
||||
# last-updated: {YYYY-MM-DD}
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entry schema
|
||||
|
||||
```yaml
|
||||
---
|
||||
task-class: {task-class-name}
|
||||
spore-file: spores/{task-class}-spores.md
|
||||
status: active | provisional | superseded
|
||||
deposited: {YYYY-MM-DD}
|
||||
supersedes: {path-to-old-spore-file} | null
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
[1-3 sentences. What situation generated this decision class. What the module
|
||||
was doing when this task class was first encountered. Why a spore file was
|
||||
needed rather than relying on the scion's base reasoning.]
|
||||
|
||||
## Reasoning
|
||||
|
||||
[The parent's full reasoning. Why each policy in the spore file was chosen.
|
||||
What edge cases were considered. What alternatives were rejected and why.
|
||||
Write for a frontier model reading this months later with no conversation
|
||||
context. This section has no token constraint — optimize for completeness.]
|
||||
|
||||
## Revision triggers
|
||||
|
||||
[Bullet list of conditions that would make this entry stale or require
|
||||
a new spore file to be composed:]
|
||||
- {Condition 1}
|
||||
- {Condition 2}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Status definitions
|
||||
|
||||
`active` — the spore file has been used by at least one scion run without
|
||||
unexpected escalation on covered decisions. Treat as current.
|
||||
|
||||
`provisional` — newly composed, not yet validated in production. The commissioning
|
||||
skill should flag after first scion run. Promote to `active` if the scion
|
||||
completed its task without escalating on covered decisions. If unexpected
|
||||
escalations occurred, update the spore file and seed entry before promoting.
|
||||
|
||||
`superseded` — this entry has been replaced by a newer one. The `supersedes`
|
||||
field in the newer entry points to the old spore file path. Retain superseded
|
||||
entries for audit — do not delete. Skip during routing.
|
||||
|
||||
---
|
||||
|
||||
## Routing rules for the commissioning skill
|
||||
|
||||
The commissioning skill scans frontmatter only on first pass. The scan should:
|
||||
|
||||
1. Filter by `task-class` matching the current task
|
||||
2. Filter by `status` not equal to `superseded`
|
||||
3. If a match is found, read `spore-file` to get the path
|
||||
4. Read the entry body only if the parent needs to review or revise the policy
|
||||
|
||||
The body is for deliberate review, not for automated routing. Do not load
|
||||
entry bodies into the routing scan — they are for the parent, not the pipeline.
|
||||
|
||||
---
|
||||
|
||||
## Full SEEDS.md example
|
||||
|
||||
```markdown
|
||||
# SEEDS.md
|
||||
# module: data-pipeline
|
||||
# maintained-by: parent agent
|
||||
# last-updated: 2026-03-20
|
||||
|
||||
---
|
||||
task-class: annotator
|
||||
spore-file: spores/annotator-spores.md
|
||||
status: active
|
||||
deposited: 2026-03-20
|
||||
supersedes: null
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The data-pipeline module processes raw input files and produces annotated JSONL
|
||||
output. Annotator scions handle the core transformation. The first annotator
|
||||
scion run encountered a write attempt to a .env file and an ambiguous output
|
||||
format question — both were resolved and crystallized as spore entries.
|
||||
|
||||
## Reasoning
|
||||
|
||||
Credential file protection: the annotator task class has no legitimate reason
|
||||
to write to .env or secrets files. The decision to deny unconditionally (rather
|
||||
than escalate) was made because even a legitimate-seeming reason from a Haiku-class
|
||||
scion is not sufficient justification for credential file access — escalate for
|
||||
that. A hook was generated because the condition is fully deterministic.
|
||||
|
||||
Output format: JSONL was chosen over JSON array because annotator runs are
|
||||
long-running and JSONL allows streaming consumption by downstream validators
|
||||
without waiting for run completion. This is a module convention, not a general
|
||||
principle — other modules may differ.
|
||||
|
||||
Bash guard: the annotator task class requires read and write access but never
|
||||
needs to execute shell commands. The deny-all-bash policy was chosen over a
|
||||
more permissive pattern because any bash requirement from an annotator scion
|
||||
is unexpected and warrants parent review.
|
||||
|
||||
## Revision triggers
|
||||
|
||||
- If the module adds a legitimate use case for bash execution in annotation
|
||||
- If the output format changes from JSONL to another format
|
||||
- If credential file patterns change (new file types added to the project)
|
||||
- If annotator task class is split into sub-classes with different permission needs
|
||||
|
||||
---
|
||||
task-class: validator
|
||||
spore-file: spores/validator-spores.md
|
||||
status: provisional
|
||||
deposited: 2026-03-20
|
||||
supersedes: null
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Validator scions check annotator output against a schema. First commissioned
|
||||
2026-03-20. Not yet run in production — status provisional pending first run.
|
||||
|
||||
## Reasoning
|
||||
|
||||
Read-only access: validators should not write to source files. The deny-write
|
||||
policy covers all paths except the validator's own output directory. This is
|
||||
conservative — if a legitimate write need emerges, the scion will escalate and
|
||||
the policy can be revised.
|
||||
|
||||
Schema mismatch escalation: rather than having the validator silently fail or
|
||||
produce empty output on schema mismatch, escalation was chosen so the parent
|
||||
can decide whether the schema has drifted or the annotation has an error.
|
||||
|
||||
## Revision triggers
|
||||
|
||||
- After first production run — review escalation rate; if too high, the
|
||||
schema-mismatch trigger may be too sensitive
|
||||
- If validator gains write permissions to a specific output path
|
||||
```
|
||||
90
skills/commissioning-skill/skill-scout.md
Normal file
90
skills/commissioning-skill/skill-scout.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# skill-scout — commissioning skill extension
|
||||
# Fetches purpose-specific external skills at commission time.
|
||||
# Add this capability to the commissioning skill Step 3 (compose path)
|
||||
# whenever a builder scion is being commissioned.
|
||||
|
||||
---
|
||||
|
||||
## When to run skill-scout
|
||||
|
||||
Run skill-scout before commissioning any builder scion. The sorter scion
|
||||
and commit-tracker hook do not require external skills — they are covered
|
||||
by their spore files alone. The builder scion does, because the quality
|
||||
of its output depends on the aesthetic and technical conventions of the
|
||||
build type (frontend-design, annotated-writing, etc.).
|
||||
|
||||
---
|
||||
|
||||
## Skill-scout decision table
|
||||
|
||||
| Section being built | Recommended external skill | Source |
|
||||
|------------------------|--------------------------------------|---------------------------------|
|
||||
| Home page (star-map) | frontend-design + ux-design | anthropics/skills + wondelai |
|
||||
| Writings | annotated-writing | project skills (user-supplied) |
|
||||
| Videos | frontend-design | anthropics/skills |
|
||||
| Images (slideshow) | frontend-design | anthropics/skills |
|
||||
| Playlists / Watchlists | frontend-design | anthropics/skills |
|
||||
| Music gallery | frontend-design | anthropics/skills |
|
||||
| Creature Playground | commissioning-skill (recursive) | this workspace |
|
||||
| Any section (UI/UX) | ux-design (refactoring-ui + ux-heuristics) | wondelai/skills |
|
||||
|
||||
---
|
||||
|
||||
## Skill-scout process
|
||||
|
||||
1. Identify the section being built from the ToBuild/{section}/ path
|
||||
2. Look up the recommended skill from the decision table above
|
||||
3. Check whether the skill is already present in commissioning-skill/fetched-skills/
|
||||
4. If absent: fetch from source
|
||||
- For GitHub-hosted skills: clone or download the SKILL.md (and references/ if present)
|
||||
- Save to commissioning-skill/fetched-skills/{skill-name}/
|
||||
5. Set SCION_EXTERNAL_SKILL env var to the fetched skill path before spawning builder
|
||||
6. Include fetched skill contents in the builder scion's commission package
|
||||
|
||||
## Fetched skill cache
|
||||
|
||||
```
|
||||
commissioning-skill/
|
||||
└── fetched-skills/
|
||||
├── frontend-design/
|
||||
│ └── SKILL.md
|
||||
├── annotated-writing/
|
||||
│ ├── SKILL.md
|
||||
│ └── references/
|
||||
└── ux-design/
|
||||
└── SKILL.md
|
||||
```
|
||||
|
||||
Fetched skills are cached between commission cycles. Do not re-fetch on
|
||||
every commission — check the cache first. Re-fetch only if the skill file
|
||||
is older than 7 days or if the parent explicitly requests a refresh.
|
||||
|
||||
---
|
||||
|
||||
## Skill-scout for Creature Playground
|
||||
|
||||
The Creature Playground section is special: scions build there autonomously.
|
||||
The parent commissions a builder scion with the commissioning-skill itself
|
||||
bundled (recursive commissioning). The scion can then commission sub-scions
|
||||
of its own within the Playground scope. Output is for review only — nothing
|
||||
in Creature Playground is deployed to the live site without explicit parent
|
||||
review and JL's commit trigger.
|
||||
|
||||
---
|
||||
|
||||
## Integration point in commissioning skill
|
||||
|
||||
Add between Step 3b (reason through decision surface) and Step 3c (write
|
||||
spore entries) for builder task class:
|
||||
|
||||
```
|
||||
3b-scout: If task-class is builder:
|
||||
- Identify section from ToBuild path
|
||||
- Run skill-scout to fetch appropriate external skill
|
||||
- Set SCION_EXTERNAL_SKILL to fetched skill path
|
||||
- Proceed to 3c with external skill context available
|
||||
```
|
||||
|
||||
The external skill content should be appended to the builder scion's spore
|
||||
file reference list, not embedded inline — keep the spore file itself within
|
||||
its token budget.
|
||||
170
skills/commissioning-skill/spore-entry-format.md
Normal file
170
skills/commissioning-skill/spore-entry-format.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# Spore entry format
|
||||
|
||||
Each entry in a `*-spores.md` file is a self-contained, atomic policy record.
|
||||
One trigger condition. One outcome. No prose reasoning.
|
||||
|
||||
---
|
||||
|
||||
## Schema
|
||||
|
||||
```yaml
|
||||
---
|
||||
spore: {kebab-case-identifier}
|
||||
task-class: {task-class-name}
|
||||
scion-model: {model-identifier}
|
||||
deposited: {YYYY-MM-DD}
|
||||
hook: {relative-path-to-hook} | nil
|
||||
---
|
||||
policy: {single declarative sentence describing the decision}
|
||||
trigger: {event} · {condition}
|
||||
on-match: allow | deny | escalate
|
||||
on-novel: escalate to parent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Field definitions
|
||||
|
||||
`spore` — unique identifier within the file. Kebab-case. Descriptive enough to
|
||||
identify the decision at a glance without reading the body.
|
||||
|
||||
`task-class` — the task class this entry was written for. Should match the
|
||||
filename stem (e.g. `annotator` in `annotator-spores.md`).
|
||||
|
||||
`scion-model` — the model this entry was token-budgeted for. Entries written for
|
||||
Haiku-4.5 are valid for Sonnet-class scions (more capable model, easier load)
|
||||
but not necessarily vice versa.
|
||||
|
||||
`deposited` — ISO date the entry was first written.
|
||||
|
||||
`hook` — relative path to the compiled cchooks Python file, if one exists for
|
||||
this decision. `nil` if the decision requires scion interpretation and has no
|
||||
compiled hook.
|
||||
|
||||
`policy` — one declarative sentence. What the scion should do. Not why. Not when.
|
||||
Just what. The trigger handles when.
|
||||
|
||||
`trigger` — the event and condition that activates this policy. Use the format:
|
||||
`{HookEvent} · {condition}`. Hook events are: `PreToolUse`, `PostToolUse`,
|
||||
`UserPromptSubmit`, `Stop`, `SessionStart`, `SessionEnd`.
|
||||
|
||||
`on-match` — the outcome when trigger fires: `allow`, `deny`, or `escalate`.
|
||||
|
||||
`on-novel` — always `escalate to parent`. This is the fallback for situations
|
||||
not covered by any spore entry.
|
||||
|
||||
---
|
||||
|
||||
## Token discipline
|
||||
|
||||
Each entry must stay under approximately 60 tokens. Count before writing.
|
||||
The policy line is the most likely offender — keep it to one clause.
|
||||
The trigger line should be terse: event type, one condition, nothing more.
|
||||
|
||||
Reasoning about *why* a policy exists belongs in the corresponding SEEDS.md
|
||||
entry body, not here. If you find yourself wanting to add a comment or
|
||||
explanation to a spore entry, that content goes in SEEDS.md instead.
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### File write guard (with hook)
|
||||
|
||||
```yaml
|
||||
---
|
||||
spore: env-file-write-guard
|
||||
task-class: annotator
|
||||
scion-model: haiku-4.5
|
||||
deposited: 2026-03-20
|
||||
hook: hooks/env-guard.py
|
||||
---
|
||||
policy: deny all writes to sensitive credential files
|
||||
trigger: PreToolUse · tool=Write · file_path matches *.env|secrets.json|id_rsa
|
||||
on-match: deny
|
||||
on-novel: escalate to parent
|
||||
```
|
||||
|
||||
### Output format enforcement (no hook)
|
||||
|
||||
```yaml
|
||||
---
|
||||
spore: annotation-output-format
|
||||
task-class: annotator
|
||||
scion-model: haiku-4.5
|
||||
deposited: 2026-03-20
|
||||
hook: nil
|
||||
---
|
||||
policy: write all annotation output as JSONL to stdout, one record per line
|
||||
trigger: PostToolUse · tool=Write · file_path matches *.annotation
|
||||
on-match: allow
|
||||
on-novel: escalate to parent
|
||||
```
|
||||
|
||||
### Bash safety guard (with hook)
|
||||
|
||||
```yaml
|
||||
---
|
||||
spore: destructive-bash-guard
|
||||
task-class: annotator
|
||||
scion-model: haiku-4.5
|
||||
deposited: 2026-03-20
|
||||
hook: hooks/bash-guard.py
|
||||
---
|
||||
policy: deny bash commands containing destructive patterns
|
||||
trigger: PreToolUse · tool=Bash · command matches rm -rf|sudo|fdisk|format
|
||||
on-match: deny
|
||||
on-novel: escalate to parent
|
||||
```
|
||||
|
||||
### Escalation trigger (no hook)
|
||||
|
||||
```yaml
|
||||
---
|
||||
spore: schema-mismatch-escalate
|
||||
task-class: annotator
|
||||
scion-model: haiku-4.5
|
||||
deposited: 2026-03-20
|
||||
hook: nil
|
||||
---
|
||||
policy: escalate if input schema does not match expected annotation schema
|
||||
trigger: PreToolUse · tool=Read · file_path matches *.input · schema-mismatch detected
|
||||
on-match: escalate
|
||||
on-novel: escalate to parent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Full spore file example
|
||||
|
||||
```markdown
|
||||
# annotator-spores.md
|
||||
# task-class: annotator
|
||||
# scion-model: haiku-4.5
|
||||
# token-budget: 600
|
||||
# last-updated: 2026-03-20
|
||||
|
||||
---
|
||||
spore: env-file-write-guard
|
||||
task-class: annotator
|
||||
scion-model: haiku-4.5
|
||||
deposited: 2026-03-20
|
||||
hook: hooks/env-guard.py
|
||||
---
|
||||
policy: deny all writes to sensitive credential files
|
||||
trigger: PreToolUse · tool=Write · file_path matches *.env|secrets.json|id_rsa
|
||||
on-match: deny
|
||||
on-novel: escalate to parent
|
||||
|
||||
---
|
||||
spore: annotation-output-format
|
||||
task-class: annotator
|
||||
scion-model: haiku-4.5
|
||||
deposited: 2026-03-20
|
||||
hook: nil
|
||||
---
|
||||
policy: write annotation output as JSONL to stdout, one record per line
|
||||
trigger: PostToolUse · tool=Write · file_path matches *.annotation
|
||||
on-match: allow
|
||||
on-novel: escalate to parent
|
||||
```
|
||||
78
skills/commissioning-skill/token-budgets.md
Normal file
78
skills/commissioning-skill/token-budgets.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Token budgets
|
||||
|
||||
Token budgets for spore files are set by the parent agent at commission time
|
||||
based on the scion's model class. The goal is not to minimise tokens — it is
|
||||
to maximise signal density within the model's reliable execution window.
|
||||
|
||||
---
|
||||
|
||||
## Why this matters
|
||||
|
||||
Context rot is empirically universal: every model degrades before its nominal
|
||||
context window limit, and smaller models degrade faster. A dense, denoised
|
||||
500-token spore file produces better scion outputs than a verbose 2,000-token
|
||||
one — not just cheaper outputs. Signal density is a quality constraint, not
|
||||
only a cost constraint.
|
||||
|
||||
Source: Chroma research 2025; Liu et al. "Lost in the Middle" 2024;
|
||||
CompLLM 2025 (compressed context outperforming uncompressed on small models).
|
||||
|
||||
The nominal context window is almost irrelevant. What matters is the model's
|
||||
reliable execution window — where multi-step reasoning remains coherent. For
|
||||
agentic tasks this is a fraction of the nominal limit.
|
||||
|
||||
---
|
||||
|
||||
## Per-model ceilings
|
||||
|
||||
These are working estimates. Not empirically tested against this architecture.
|
||||
Measure scion task success rate as a function of spore file size for your
|
||||
specific task classes, then adjust.
|
||||
|
||||
| Scion model class | Nominal window | Spore file ceiling | Per-entry target |
|
||||
|-----------------------|----------------|-------------------|-----------------|
|
||||
| Haiku 4.5 | 200K | 600 tokens | ~60 tokens |
|
||||
| Gemini 2.5 Flash | 1M | 800 tokens | ~60 tokens |
|
||||
| Sonnet-class scion | 200K | 1,200 tokens | ~80 tokens |
|
||||
|
||||
The ceiling applies to the full spore file content, not per entry.
|
||||
At 60 tokens per entry, a 600-token ceiling accommodates approximately
|
||||
8-10 entries — enough for most well-scoped task classes.
|
||||
|
||||
---
|
||||
|
||||
## Frontier model note
|
||||
|
||||
A frontier parent reading a spore file written for Haiku-class scions will
|
||||
process it with less interpretive load, not more. The density that is required
|
||||
for a small model is simply easier for a large model. There is no upper bound
|
||||
on how dense context can be for a frontier model — only a lower bound on how
|
||||
dense it must be for a small one.
|
||||
|
||||
This means spore files composed for the smallest expected scion are valid for
|
||||
any larger scion that might also read them. Write for the least capable consumer.
|
||||
|
||||
---
|
||||
|
||||
## Prompt caching note
|
||||
|
||||
Spore files are near-perfect prompt caching targets because they change slowly
|
||||
and load at the beginning of the scion's context. Anthropic's prompt caching
|
||||
charges 10% of base input token price on cache hits. A stable spore file
|
||||
achieves near-100% cache hit rate — the token cost is effectively paid once
|
||||
across all scion runs in its task class.
|
||||
|
||||
Keep spore file content stable between runs. Avoid injecting timestamps,
|
||||
run IDs, or other variable content into the spore file — variable content
|
||||
breaks the cache prefix and forces a full re-read on every run.
|
||||
|
||||
---
|
||||
|
||||
## Counting tokens
|
||||
|
||||
Rough heuristic for English prose: 1 token ≈ 0.75 words, or ~4 characters.
|
||||
A 60-token entry is approximately 45 words or 240 characters. Write an entry,
|
||||
count the words, multiply by 1.33 — if the result exceeds 60, trim.
|
||||
|
||||
For precise counts, use the Anthropic tokenizer or the `tiktoken` library
|
||||
(cl100k_base encoding as a close approximation for Claude models).
|
||||
Reference in New Issue
Block a user