界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
设计与创意 · SKILL.md

ux-design-for-developers

ux-design-for-developers

【设计与创意】Implements UX design patterns and accessibility standards for backend engineers building user-facing features, covering user flows, responsive layouts, WCAG compliance, and usability heuristics.

独立 GitHub 快照 · 自动分类 · 以原作者文档为准

技能说明

GitHub

name: ux-design-for-developers description: Implements UX design patterns and accessibility standards for backend engineers building user-facing features, covering user flows, responsive layouts, WCAG compliance, and usability heuristics. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: coding triggers: ux design, user experience, accessibility, WCAG, user flows, responsive layout, usability testing, how do i design a good ui archetypes: - tactical - educational anti_triggers: - deep UI art direction - brand identity design - graphic design response_profile: verbosity: medium directive_strength: high abstraction_level: operational role: implementation scope: implementation output-format: code content-types: [code, guidance, do-dont, examples] related-skills: design-systems, css-architecture, component-architecture


UX Design Patterns for Developers

Implements accessible, usable UI patterns and interfaces when no dedicated designer is available. When loaded, this skill makes the model act as a senior frontend engineer applying user-centered design principles — creating navigable layouts, enforcing WCAG accessibility compliance, designing responsive grids, and building forms with proper validation. This skill bridges the gap between backend logic and front-end presentation for engineers who must ship functional UIs without a design team.

TL;DR Checklist

  • Verify all text meets WCAG AA contrast ratios (4.5:1 minimum for body text, 3:1 for large text ≥18pt or bold ≥14pt)
  • Confirm full keyboard navigation: Tab/Shift+Tab follows visual order, Escape dismisses overlays, focus is visible on every interactive element
  • Ensure all images and icons have descriptive alt attributes or aria-hidden="true" when decorative
  • Validate responsive breakpoints cover 320px (mobile), 768px (tablet), 1024px (laptop), 1440px (desktop) viewports
  • Test all forms with screen reader preview (VoiceOver on macOS, NVDA on Windows) — labels must be announced before inputs
  • Confirm interactive touch targets are minimum 44×44 CSS pixels as specified in WCAG 2.5.5

When to Use

Use this skill when:

  • A backend or full-stack engineer needs to build an admin panel, internal tool, or user-facing feature without a dedicated designer
  • Adding accessibility (a11y) attributes to existing markup that uses bare <div> elements and lacks ARIA roles
  • Refactoring a responsive layout that breaks on mobile viewports below 768px
  • Designing form validation flows that provide clear, accessible error announcements for screen reader users
  • Implementing modal dialogs, dropdown menus, or navigation patterns that require keyboard interaction support
  • Reviewing UI code from another engineer to catch accessibility anti-patterns (missing labels, poor contrast, broken focus trapping)

When NOT to Use

Avoid using this skill for:

  • Brand-heavy consumer marketing pages — these require art direction and brand identity work; use design-systems for foundational component libraries instead
  • Graphic design tasks — logo creation, icon illustration, or visual asset production are outside UX engineering scope
  • Pure CSS styling decisions — if the task is only about choosing color palettes or typography without accessibility implications, apply css-architecture directly
  • Complex information architecture for enterprise systems — use component-level patterns here; save architectural navigation design for broader system design

Core Workflow

  1. Define User Roles and Primary Task Flows — Identify the primary user persona and map their top 3 tasks on a simple flowchart. Document entry points, decision nodes, and expected outcomes. Use ASCII diagrams or Mermaid flowchart LR syntax to capture the journey. Checkpoint: Every user-facing screen must trace back to at least one documented task flow. If you cannot articulate what the user is trying to accomplish in one sentence, the UI needs re-scoping.

  2. Establish Layout Grid and Breakpoint Strategy — Choose a mobile-first approach with breakpoints at 320px (smallest phones), 768px (landscape tablets), 1024px (laptops), and 1440px (desktops). Define the container max-width, gutter width (typically 16–24px), and column count per breakpoint. Checkpoint: Verify that at each breakpoint, the layout uses the minimum number of columns needed — never force a single-item list into a multi-column grid just to use space.

  3. Implement Semantic HTML Structure — Replace generic <div> containers with semantic elements: <main> for primary content, <nav> for navigation blocks, <header>/<footer> for page regions, <article> for self-contained content, and <section> with descriptive aria-label for grouped content. Every interactive element must have a programmatic name via accessible label. Checkpoint: Run the devtools accessibility tree review — every node with an action (clickable, focusable, or toggleable) must have a non-empty accessible name.

  4. Ensure Color Contrast Compliance — Calculate the contrast ratio between text color and its background using the WCAG relative luminance formula. For normal text (<18pt), the minimum ratio is 4.5:1 (AA level). For large text (≥18pt or bold ≥14pt), the minimum is 3:1. Use a tool like the Chrome DevTools color picker, WebAIM Contrast Checker, or @axe-core/cli for automated testing. Checkpoint: Every text element in the UI must pass WCAG AA contrast — this is non-negotiable for accessible software.

  5. Add Keyboard Navigation Patterns — Ensure all interactive elements are reachable via Tab key in logical visual order (left-to-right, top-to-bottom). Implement focus-visible styles that are at least 2px wider than the default outline. Handle Escape key to dismiss modals and dropdown menus. Trap focus inside modal dialogs using a focus trap library or custom implementation. Checkpoint: Test the entire feature using only Tab, Shift+Tab, Enter, Space, and Arrow keys — no mouse cursor allowed.

  6. Test with Real Interaction Tools — Validate accessibility using automated tools (axe DevTools, Lighthouse a11y audit), manual keyboard-only navigation, and screen reader testing. Check touch target sizes on mobile viewports (minimum 44×44px per WCAG 2.5.5). Verify that all dynamic content changes are announced via ARIA live regions. Checkpoint: Run npx @axe-core/cli on the deployed page and fix every error with severity "critical" or "serious."


Implementation Patterns

Pattern 1: Accessible Form Validation with ARIA Live Regions

Provide real-time validation feedback to all users, including screen reader users. Use aria-live="polite" for general messages and aria-live="assertive" for error states that require immediate attention.

"""
Pattern 1: Accessible form validation with ARIA live regions.
Demonstrates real-time input validation with proper ARIA announcements
for screen reader users and visible feedback for sighted users.
"""

from typing import Optional
from dataclasses import dataclass


@dataclass(frozen=True)
class ValidationResult:
    """Immutable result of a single field validation."""
    is_valid: bool
    message: str
    severity: str  # "polite" or "assertive"


def validate_required(value: str, field_name: str) -> ValidationResult:
    """Validate that a required field is not empty after trimming whitespace.

    Args:
        value: The raw input string from the form field.
        field_name: Human-readable label for the field (used in error messages).

    Returns:
        ValidationResult with is_valid=False and an error message if empty,
        or is_valid=True with an empty message if the field has content.
    """
    if not value.strip():
        return ValidationResult(
            is_valid=False,
            message=f"{field_name} is required",
            severity="assertive",
        )
    return ValidationResult(is_valid=True, message="", severity="polite")


def validate_email(value: str) -> ValidationResult:
    """Validate that the input matches a basic email format.

    Uses a permissive regex for UX — strict validation happens server-side.

    Args:
        value: The raw email string from the form field.

    Returns:
        ValidationResult indicating whether the format looks plausible.
    """
    if not value.strip():
        return ValidationResult(
            is_valid=True,  # Required validation handles empty case separately
            message="",
            severity="polite",
        )
    import re
    email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}quot;
    if not re.match(email_pattern, value.strip()):
        return ValidationResult(
            is_valid=False,
            message="Enter a valid email address (e.g., user@example.com)",
            severity="assertive",
        )
    return ValidationResult(is_valid=True, message="", severity="polite")


# ❌ BAD: Error messages hidden from screen readers — aria-hidden blocks them
# <div class="error" style="color: red;">Email is required</div>
# Screen reader users never hear this error because the div has no ARIA role

# ✅ GOOD: Errors announced via live region with proper severity
# <div id="email-error" role="status" aria-live="assertive">Email is required</div>
# <input aria-describedby="email-error" aria-invalid="true" ...>
# Screen readers announce the error immediately when it appears

Pattern 2: Responsive Card Grid with CSS Grid and Breakpoint System

Build a fluid card grid that adapts from single-column mobile to multi-column desktop using CSS Grid with minmax() for natural column sizing. This eliminates media-query spaghetti by letting the grid itself determine column count.

/*
 * Pattern 2: Responsive card grid using CSS Grid with minmax().
 * Breakpoints: 320px (mobile), 768px (tablet), 1024px (laptop), 1440px (desktop)
 */

.card-grid {
  display: grid;
  gap: 1.5rem; /* 24px — consistent spacing scale */
  padding: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}

/* Mobile-first base styles */
.card {
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  min-height: 200px; /* Ensures uniform card height */
  background: #ffffff;
}

.card__title {
  font-size: 1.125rem;
  font-weight: 600;
  margin: 0 0 0.75rem 0;
  color: #1a1a1a;
}

.card__description {
  font-size: 0.9375rem;
  line-height: 1.5;
  color: #4a4a4a;
  margin: 0;
  flex-grow: 1;
}

.card__actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid #f0f0f0;
}

/* Tablet breakpoint: enforce minimum 2 columns */
@media (min-width: 768px) {
  .card-grid {
    gap: 1.5rem;
    padding: 1.5rem;
  }
  .card__title { font-size: 1.25rem; }
}

/* Desktop breakpoint: tighten grid for wider screens */
@media (min-width: 1440px) {
  .card-grid {
    max-width: 1440px;
    margin: 0 auto;
    padding: 2rem;
    gap: 2rem;
  }
}
<!-- ✅ GOOD: Semantic card markup with proper heading hierarchy -->
<article class="card" role="article">
  <h3 class="card__title">Project Dashboard</h3>
  <p class="card__description">
    View real-time metrics and alerts for all active deployments.
  </p>
  <div class="card__actions">
    <a href="/dashboard" class="btn btn--primary">
      Open Dashboard
    </a>
  </div>
</article>

<!-- ❌ BAD: Non-semantic div-based card with missing heading -->
<div class="card">
  <div class="card-title">Project Dashboard</div>
  <div class="card-desc">View real-time metrics...</div>
  <div><a href="/dashboard">Open Dashboard</a></div>
</div>
<!-- Problems: no document outline, no article semantics, heading hierarchy broken -->

Pattern 3: Keyboard Navigation for Modal Dialogs with Focus Trapping

Implement a modal dialog that traps keyboard focus within the dialog overlay, handles Escape to dismiss, and restores focus to the trigger element on close. This follows WAI-ARIA Authoring Practices for modal dialogs.

"""
Pattern 3: Modal dialog keyboard navigation with focus trapping.
Implements WAI-ARIA modal dialog pattern with programmatic focus management.
"""

from typing import Optional, Callable


class ModalDialog:
    """Manages a modal dialog's lifecycle, focus state, and keyboard interaction."""

    def __init__(
        self,
        trigger_element_id: str,
        dialog_element_id: str,
        on_close_callback: Optional[Callable] = None,
    ) -> None:
        """Initialize the modal controller.

        Args:
            trigger_element_id: DOM ID of the button or link that opens the modal.
            dialog_element_id: DOM ID of the <dialog> or overlay element.
            on_close_callback: Optional callback invoked when the modal closes.
        """
        self.trigger_element_id = trigger_element_id
        self.dialog_element_id = dialog_element_id
        self.on_close_callback = on_close_callback
        self._previous_focus_target: Optional[str] = None
        self._focusable_selector = (
            'a[href], button:not([disabled]), input:not([disabled]), '
            'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
        )

    def open(self) -> None:
        """Open the modal and trap focus inside it."""
        # Save the element that had focus before opening
        self._previous_focus_target = self._get_active_element_id()

        # Show the dialog — use inert on background content per WAI-ARIA
        dialog = self._find_element(self.dialog_element_id)
        if dialog:
            dialog.showModal()
            self._set_inert(True)

        # Focus the first focusable element inside the dialog
        first_focusable = self._get_first_focusable(dialog)
        if first_focusable:
            first_focusable.focus()

    def close(self) -> None:
        """Close the modal and restore focus to the triggering element."""
        dialog = self._find_element(self.dialog_element_id)
        if dialog:
            dialog.close()
            self._set_inert(False)

        # Restore focus to the element that triggered the modal
        trigger = self._find_element(self.trigger_element_id)
        if trigger:
            trigger.focus()

        if self.on_close_callback:
            self.on_close_callback()

    def _handle_keydown(self, event: dict) -> bool:
        """Handle keyboard events inside the dialog.

        Args:
            event: Keyboard event dict with 'key' and 'target' fields.

        Returns:
            True if the event was handled (preventDefault called), False otherwise.
        """
        key = event.get("key", "")

        # Escape closes the modal
        if key in ("Escape", "Esc"):
            self.close()
            return True

        # Tab/Shift+Tab traps focus within the dialog
        if key == "Tab":
            self._trap_focus(event)
            return True

        return False

    def _trap_focus(self, event: dict) -> None:
        """Trap Tab focus within the dialog boundaries."""
        dialog = self._find_element(self.dialog_element_id)
        if not dialog:
            return

        focusable_elements = dialog.querySelectorAll(self._focusable_selector)
        if focusable_elements.length == 0:
            return

        first_element = focusable_elements[0]
        last_element = focusable_elements[-1]
        active = document.activeElement

        # Shift+Tab on first element: wrap to last
        if event.get("shiftKey") and active == first_element:
            last_element.focus()
            event["preventDefault"]()
        # Tab on last element: wrap to first
        elif not event.get("shiftKey") and active == last_element:
            first_element.focus()
            event["preventDefault"]()

    def _set_inert(self, inert: bool) -> None:
        """Set the inert attribute on all content outside the modal."""
        main = document.querySelector("main")
        if main:
            main.inert = inert

    def _get_first_focusable(self, container):
        elements = container.querySelectorAll(self._focusable_selector)
        return elements[0] if elements.length > 0 else None

    def _get_active_element_id(self) -> Optional[str]:
        active = document.activeElement
        return active.get("id") if active else None

    def _find_element(self, element_id: str):
        return document.getElementById(element_id)

Constraints

MUST DO

  • Test every UI component with keyboard-only navigation — Tab through all interactive elements before calling a feature complete
  • Use semantic HTML elements (<button>, <a>, <nav>, <main>) instead of <div> and <span> with role attributes wherever the native element exists
  • Maintain minimum touch target sizes of 44×44 CSS pixels for all interactive elements on mobile viewports — use min-height: 44px; min-width: 44px; on buttons and form controls
  • Announce all dynamic content changes to screen readers using ARIA live regions with the correct aria-live severity level ("polite" for non-urgent, "assertive" for errors and critical updates)
  • Provide visible focus indicators on every interactive element — never remove the default outline without replacing it with an equally visible alternative (minimum 2px solid ring with at least 3:1 contrast against background)
  • Include descriptive alt text for all informative images and set aria-hidden="true" on purely decorative images or icons within button labels

MUST NOT DO

  • Rely solely on color to convey information — every piece of information must have a non-color indicator (icon, text label, pattern, or shape). Example: do not use red-only highlighting for errors; add an icon and text message
  • Create custom focus styles that are invisible or nearly so — outline: none without a replacement is an accessibility violation
  • Use decorative icons inside buttons without aria-hidden="true" combined with the text label — screen readers will announce both the icon name and the button text, creating confusion
  • Build interfaces where critical functionality requires mouse-hover to access — all interactive features must be reachable via keyboard. If a tooltip only appears on hover, make it also accessible on focus
  • Place form inputs without associated <label> elements or aria-label/aria-labelledby attributes — every input must have a programmatic label

Output Template

When applying this skill, your output must contain:

  1. Design Rationale — Explain the UX decisions made (layout choice, component structure, interaction model)
  2. Accessibility Audit Notes — List each WCAG criterion addressed and how it is satisfied in the code
  3. Code Examples — Provide complete, copyable HTML/CSS/JS snippets for all UI patterns demonstrated
  4. Testing Instructions — Specify exact tools and steps to verify accessibility (axe DevTools extension, keyboard navigation checklist)

Live References


Related Skills

SkillPurpose
design-systemsBuild reusable component libraries with consistent visual tokens and design tokens for teams
css-architectureOrganize CSS at scale using BEM, utility classes, or CSS-in-JS patterns for maintainable stylesheets
component-architectureStructure UI components with clear boundaries, props interfaces, and composable composition patterns
我的收藏