Claude Code Plugins

Community-maintained marketplace

Feedback

Navigation and user flow patterns including routing, state management, progressive disclosure, and game progression. Use when designing multi-step flows, game phases, or navigation structures. (project)

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name ux-user-flow
description Navigation and user flow patterns including routing, state management, progressive disclosure, and game progression. Use when designing multi-step flows, game phases, or navigation structures. (project)
allowed-tools Read, Write, Edit, Glob, Grep

UX User Flow Skill

Navigation architecture and user journey patterns for single-page game applications. This skill covers routing, state-driven UI, and progressive disclosure.

State-Based Routing

Game State Machine

const GAME_STATES = {
  loading: 'game-loading',
  playing: 'game-playing',
  paused: 'game-paused',
  complete: 'game-complete',
  menu: 'game-menu'
};

class GameRouter {
  #state = GAME_STATES.loading;

  get state() {
    return this.#state;
  }

  set state(newState) {
    const oldState = this.#state;
    this.#state = newState;

    document.body.dataset.state = newState;

    this.dispatchEvent(new CustomEvent('state-change', {
      detail: { from: oldState, to: newState }
    }));
  }
}

CSS State Display

/* Hide all views by default */
[data-view] {
  display: none;
}

/* Show view matching state */
[data-state="game-loading"] [data-view="loading"],
[data-state="game-playing"] [data-view="playing"],
[data-state="game-menu"] [data-view="menu"] {
  display: block;
}

URL Hash Routing

class HashRouter {
  constructor() {
    window.addEventListener('hashchange', () => this.#handleRoute());
    this.#handleRoute();
  }

  #handleRoute() {
    const hash = window.location.hash.slice(1) || 'home';
    this.dispatchEvent(new CustomEvent('route', {
      detail: { route: hash }
    }));
  }

  navigate(route) {
    window.location.hash = route;
  }
}

Game Phase Navigation

Phase Progression

const PHASES = ['word', 'collision', 'mutation', 'story'];

class PhaseManager {
  #currentPhase = 'word';
  #completedPhases = new Set();

  get currentPhase() {
    return this.#currentPhase;
  }

  canAccess(phase) {
    const index = PHASES.indexOf(phase);
    if (index === 0) return true;
    const prevPhase = PHASES[index - 1];
    return this.#completedPhases.has(prevPhase);
  }

  completePhase(phase) {
    this.#completedPhases.add(phase);
    const nextIndex = PHASES.indexOf(phase) + 1;
    if (nextIndex < PHASES.length) {
      this.#currentPhase = PHASES[nextIndex];
    }
  }
}

Phase Indicator UI

<phase-indicator
  phases="word,collision,mutation,story"
  current="word">
</phase-indicator>

Multi-Step Flows

Wizard Pattern

Steps are stored as direct references during construction - NO querySelector:

class StepWizard extends HTMLElement {
  #steps = [];  // Direct element references
  #currentStep = 0;

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    // Build steps during construction, store direct references
    const stepData = ['Step 1', 'Step 2', 'Step 3'];
    stepData.forEach(title => {
      const step = document.createElement('div');
      step.className = 'step';
      step.setAttribute('part', 'step');
      step.textContent = title;
      this.#steps.push(step);  // Store direct reference
      this.shadowRoot.appendChild(step);
    });

    this.#updateView();
  }

  connectedCallback() {
    this.addEventListener('click', this);
    this.addEventListener('keydown', this);
  }

  disconnectedCallback() {
    this.removeEventListener('click', this);
    this.removeEventListener('keydown', this);
  }

  handleEvent(e) {
    // Handle navigation via events
  }

  next() {
    if (this.#currentStep < this.#steps.length - 1) {
      this.#currentStep++;
      this.#updateView();
    }
  }

  previous() {
    if (this.#currentStep > 0) {
      this.#currentStep--;
      this.#updateView();
    }
  }

  #updateView() {
    // Use direct references - never querySelector
    this.#steps.forEach((step, i) => {
      step.hidden = i !== this.#currentStep;
      step.setAttribute('aria-current', i === this.#currentStep ? 'step' : 'false');
    });
  }
}

customElements.define('step-wizard', StepWizard);

Progress Tracking

<ol class="step-progress" aria-label="Progress">
  <li data-status="complete">Create Account</li>
  <li data-status="current" aria-current="step">Choose Avatar</li>
  <li data-status="pending">Start Game</li>
</ol>

Progressive Disclosure

Reveal on Interaction

class ExpandableSection extends HTMLElement {
  handleEvent(e) {
    if (e.type === 'click') {
      const expanded = this.getAttribute('aria-expanded') === 'true';
      this.setAttribute('aria-expanded', !expanded);
      this.#content.hidden = expanded;
    }
  }
}

Conditional Rendering

render() {
  // Only show advanced options when enabled
  if (this.#showAdvanced) {
    this.#advancedSection.hidden = false;
  }

  // Unlock features based on progress
  this.#lockedFeatures.forEach(feature => {
    const isUnlocked = this.#progress >= feature.requiredProgress;
    feature.element.toggleAttribute('locked', !isUnlocked);
    feature.element.setAttribute('aria-disabled', !isUnlocked);
  });
}

Just-in-Time Help

#showHintIfNeeded() {
  const attempts = this.#failedAttempts;
  if (attempts >= 3) {
    this.#showHint('mild');
  }
  if (attempts >= 5) {
    this.#showHint('strong');
  }
}

Menu Navigation

Keyboard Navigation

handleEvent(e) {
  if (e.type === 'keydown') {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        this.#focusNext();
        break;
      case 'ArrowUp':
        e.preventDefault();
        this.#focusPrevious();
        break;
      case 'Home':
        e.preventDefault();
        this.#focusFirst();
        break;
      case 'End':
        e.preventDefault();
        this.#focusLast();
        break;
      case 'Escape':
        this.close();
        break;
    }
  }
}

Menu Structure

<nav aria-label="Game menu">
  <ul role="menu">
    <li role="none">
      <button role="menuitem" aria-current="page">Word Phase</button>
    </li>
    <li role="none">
      <button role="menuitem">Collision Phase</button>
    </li>
    <li role="none">
      <button role="menuitem" aria-disabled="true">Story Phase</button>
    </li>
  </ul>
</nav>

View Transitions

Crossfade Views

[data-view] {
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

[data-view].active {
  opacity: 1;
  pointer-events: auto;
}

Slide Transitions

[data-view] {
  transform: translateX(100%);
  transition: transform 0.3s ease;
}

[data-view].active {
  transform: translateX(0);
}

[data-view].exiting {
  transform: translateX(-100%);
}

Focus Management

On View Change

Store focus target references during construction - NO querySelector:

class ViewRouter extends HTMLElement {
  #views = new Map();       // view name -> { element, focusTarget }
  #currentView = null;

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    // Build views during construction
    const viewConfigs = [
      { name: 'home', title: 'Home' },
      { name: 'game', title: 'Game' },
      { name: 'settings', title: 'Settings' }
    ];

    viewConfigs.forEach(config => {
      const view = document.createElement('div');
      view.className = 'view';
      view.setAttribute('data-view', config.name);

      // Create and store focus target reference
      const heading = document.createElement('h1');
      heading.textContent = config.title;
      heading.setAttribute('tabindex', '-1');  // Focusable but not in tab order
      view.appendChild(heading);

      // Store both view and focus target as direct references
      this.#views.set(config.name, {
        element: view,
        focusTarget: heading  // Direct reference for focus
      });

      this.shadowRoot.appendChild(view);
    });
  }

  #navigateTo(viewName) {
    // Hide current view
    if (this.#currentView) {
      this.#currentView.element.hidden = true;
    }

    // Show new view
    const view = this.#views.get(viewName);
    if (view) {
      view.element.hidden = false;
      this.#currentView = view;

      // Focus using direct reference - NO querySelector
      view.focusTarget?.focus();
    }
  }
}

Skip Links

<a href="#main-game" class="skip-link">Skip to game</a>
<a href="#word-list" class="skip-link">Skip to word list</a>

History Management

Browser Back/Forward

class HistoryManager {
  pushState(state, title, url) {
    history.pushState(state, title, url);
  }

  connectedCallback() {
    window.addEventListener('popstate', (e) => {
      if (e.state) {
        this.#restoreState(e.state);
      }
    });
  }

  #restoreState(state) {
    this.dispatchEvent(new CustomEvent('history-navigate', {
      detail: state
    }));
  }
}

Game Progression Patterns

Save Points

async saveProgress() {
  const progress = {
    phase: this.#currentPhase,
    completedWords: Array.from(this.#completedWords),
    score: this.#score,
    timestamp: Date.now()
  };

  await this.#storage.set('progress', progress);
}

async loadProgress() {
  const progress = await this.#storage.get('progress');
  if (progress) {
    this.#restoreProgress(progress);
  }
}

Session Continuity

connectedCallback() {
  // Check for saved session
  this.#checkSavedSession();
}

#checkSavedSession() {
  const saved = this.#storage.get('session');
  if (saved) {
    this.#showResumePrompt(saved);
  }
}

#showResumePrompt(session) {
  this.dispatchEvent(new CustomEvent('resume-available', {
    detail: {
      phase: session.phase,
      progress: session.progress
    }
  }));
}

Error Recovery

Graceful Degradation

async #loadPhase(phase) {
  try {
    const data = await this.#fetchPhaseData(phase);
    this.#renderPhase(data);
  } catch (error) {
    this.#showErrorRecovery({
      message: 'Failed to load phase',
      retry: () => this.#loadPhase(phase),
      fallback: () => this.#loadOfflineData(phase)
    });
  }
}

Offline Support

if (!navigator.onLine) {
  this.#showOfflineMode();
  this.#loadCachedContent();
}

window.addEventListener('online', () => {
  this.#syncProgress();
  this.#showOnlineMode();
});