mcpField
G
generic-react-ux-designer

generic-react-ux-designer

github.com/travisjneumanDesign & UI106246 installsUpdated 1d ago
Repository

About generic-react-ux-designer

generic-react-ux-designer is a UX skill for React and TypeScript application design, extending the Generic UX Designer skill. It focuses on interaction, accessibility, and common interface states through React-oriented patterns and checklists.

Key features

  1. 01React interaction patterns
  2. 02Loading and optimistic UI guidance
  3. 03Focus and keyboard navigation
  4. 04Motion accessibility practices
  5. 05Modal and dialog patterns
  6. 06Empty and error state guidance

Use cases

  1. 01Designing feedback for asynchronous actions
  2. 02Improving keyboard access in React interfaces
  3. 03Building accessible modal dialogs
  4. 04Reviewing forms and interface states

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/travisjneuman/.claude --skill generic-react-ux-designer

After installing, type/generic-react-ux-designerin Claude Code to confirm it loaded.

Details

From the project README

React UX Designer

Professional UX expertise for React/TypeScript applications.

Extends: Generic UX Designer - Read base skill for design thinking process, user psychology, heuristic evaluation, and research methods.

React Interaction Patterns

Micro-interactions with Framer Motion
// Checkbox animation
<motion.div
  animate={{ scale: checked ? 1 : 0 }}
  transition={{ type: "spring", stiffness: 500 }}
>
  <Check className="w-4 h-4" />
</motion.div>

// Button press feedback
<motion.button
  whileTap={{ scale: 0.98 }}
  whileHover={{ scale: 1.02 }}
  transition={{ type: "spring", stiffness: 400 }}
>
  Click me
</motion.button>

// Toast notification
<motion.div
  initial={{ opacity: 0, y: 50 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0, y: 50 }}
>
  <Toast message={message} />
</motion.div>
Loading States
// Skeleton (preferred over spinners)
<div className="animate-pulse space-y-4">
  <div className="h-8 bg-slate-200 rounded w-3/4" />
  <div className="h-4 bg-slate-200 rounded" />
</div>

// Progress indicator for long operations
<div className="relative w-full h-2 bg-slate-200 rounded">
  <motion.div
    className="absolute h-full bg-primary rounded"
    initial={{ width: 0 }}
    animate={{ width: `${progress}%` }}
  />
</div>
Optimistic UI Pattern
const handleLike = async () => {
  // Update UI immediately
  setLiked(true);
  setCount((c) => c + 1);

  try {
    await api.like(id);
  } catch {
    // Rollback on error
    setLiked(false);
    setCount((c) => c - 1);
    toast.error("Failed to save");
  }
};

React Accessibility Patterns

Focus Management
// Modal focus trap
function Modal({ isOpen, onClose, children }: ModalProps) {
  const modalRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (isOpen) {
      const firstFocusable = modalRef.current?.querySelector(
        "button, [href], input, select, textarea",
      ) as HTMLElement;
      firstFocusable?.focus();
    }
  }, [isOpen]);

  // Trap focus within modal
  const handleKeyDown = (e: KeyboardEvent) => {
    if (e.key === "Tab") {
      const focusables = modalRef.current?.querySelectorAll(
        "button, [href], input, select, textarea",
      );
      // Handle tab cycling...
    }
    if (e.key === "Escape") onClose();
  };

  return (
    <div
      ref={modalRef}
      role="dialog"
      aria-modal="true"
      onKeyDown={handleKeyDown}
    >
      {children}
    </div>
  );
}
Keyboard Navigation
// Custom keyboard shortcut
useEffect(() => {
  const handleKeyDown = (e: KeyboardEvent) => {
    if ((e.metaKey || e.ctrlKey) && e.key === "k") {
      e.preventDefault();
      openCommandPalette();
    }
  };
  window.addEventListener("keydown", handleKeyDown);
  return () => window.removeEventListener("keydown", handleKeyDown);
}, []);

// Arrow key navigation in list
const handleKeyDown = (e: KeyboardEvent, index: number) => {
  switch (e.key) {
    case "ArrowDown":
      e.preventDefault();
      focusItem(index + 1);
      break;
    case "ArrowUp":
      e.preventDefault();
      focusItem(index - 1);
      break;
  }
};
Motion Accessibility
// Respect prefers-reduced-motion
const prefersReducedMotion = window.matchMedia(
  "(prefers-reduced-motion: reduce)",
).matches;

<motion.div
  initial={{ opacity: 0, y: prefersReducedMotion ? 0 : 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}
/>;

Form UX Patterns

React Hook Form Integration
function ContactForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div className="space-y-1">
        <label htmlFor="email" className="text-sm font-medium">
          Email
        </label>
        <input
          id="email"
          {...register("email", { required: "Email is required" })}
          aria-invalid={errors.email ? "true" : "false"}
          aria-describedby={errors.email ? "email-error" : undefined}
          className={cn("input", errors.email && "border-red-500")}
        />
        {errors.email && (
          <p id="email-error" className="text-sm text-red-500">
            {errors.email.message}
          </p>
        )}
      </div>
    </form>
  );
}
Inline Validation
// Validate on blur, show on focus
const [touched, setTouched] = useState(false);
const [error, setError] = useState("");

<input
  onBlur={() => {
    setTouched(true);
    setError(validate(value));
  }}
  onFocus={() => setError("")} // Clear while editing
  className={touched && error ? "border-red-500" : ""}
/>;

Modal/Dialog Patterns

Confirmation Dialog
function ConfirmDialog({ isOpen, onConfirm, onCancel, title, message }: Props) {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
    >
      <motion.div
        initial={{ scale: 0.95 }}
        animate={{ scale: 1 }}
        role="alertdialog"
        aria-modal="true"
        aria-labelledby="dialog-title"
        aria-describedby="dialog-desc"
        className="bg-white rounded-xl p-6 max-w-md"
      >
        <h2 id="dialog-title" className="text-lg font-semibold">
          {title}
        </h2>
        <p id="dialog-desc" className="mt-2 text-muted">
          {message}
        </p>
        <div className="mt-4 flex gap-3 justify-end">
          <button onClick={onCancel} className="btn-secondary">
            Cancel
          </button>
          <button onClick={onConfirm} className="btn-primary">
            Confirm
          </button>
        </div>
      </motion.div>
    </motion.div>
  );
}
Command Palette (⌘K Pattern)
function CommandPalette({ isOpen, onClose }: Props) {
  const [query, setQuery] = useState("");
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (isOpen) inputRef.current?.focus();
  }, [isOpen]);

  const filtered = useMemo(
    () =>
      commands.filter((c) =>
        c.label.toLowerCase().includes(query.toLowerCase()),
      ),
    [query],
  );

  return (
    <div role="dialog" aria-label="Command palette">
      <input
        ref={inputRef}
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Type a command..."
        aria-autocomplete="list"
      />
      <ul role="listbox">
        {filtered.map((cmd) => (
          <li key={cmd.id} role="option">
            {cmd.label}
          </li>
        ))}
      </ul>
    </div>
  );
}

Empty & Error States

// Empty state with action
function EmptyState({ title, description, action }: Props) {
  return (
    <div className="text-center py-12">
      <Icon className="mx-auto h-12 w-12 text-muted" />
      <h3 className="mt-4 text-lg font-medium">{title}</h3>
      <p className="mt-2 text-muted">{description}</p>
      <button onClick={action.onClick} className="mt-4 btn-primary">
        {action.label}
      </button>
    </div>
  );
}

// Error state with retry
function ErrorState({ error, onRetry }: Props) {
  return (
    <div className="text-center py-12" role="alert">
      <AlertCircle className="mx-auto h-12 w-12 text-red-500" />
      <h3 className="mt-4 text-lg font-medium">Something went wrong</h3>
      <p className="mt-2 text-muted">{error.message}</p>
      <button onClick={onRetry} className="mt-4 btn-primary">
        Try again
      </button>
    </div>
  );
}

React UX Checklist

Interaction Quality:

  • Immediate feedback on user actions
  • Loading states for async operations
  • Optimistic updates where appropriate
  • Smooth animations (60fps)

Accessibility:

  • Keyboard navigation complete
  • Focus management in modals
  • Motion respects prefers-reduced-motion
  • ARIA labels on interactive elements

Forms:

  • Inline validation
  • Clear error messages
  • Smart defaults
  • Progress indication for multi-step

See Also

Comments

Markdown supported