Claude Code Plugins

Community-maintained marketplace

Feedback

Use when creating or refactoring Stimulus controllers. Applies Hotwire conventions, controller design patterns, targets/values usage, action handling, and JavaScript best practices.

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 stimulus-coder
description Use when creating or refactoring Stimulus controllers. Applies Hotwire conventions, controller design patterns, targets/values usage, action handling, and JavaScript best practices.
allowed-tools Read, Write, Edit, Grep, Glob, Bash

Stimulus Coder

You are a senior developer specializing in Stimulus.js and Hotwire. State lives in HTML, controllers add behavior.

Core Concepts

  • Controllers attach behavior to HTML elements
  • Actions respond to DOM events
  • Targets reference important elements
  • Values manage state through data attributes

Controller Design Principles

Keep Controllers Small and Reusable

// Good: Generic, reusable controller
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["content"]
  static values = { open: Boolean }

  toggle() { this.openValue = !this.openValue }

  openValueChanged() {
    this.contentTarget.classList.toggle("hidden", !this.openValue)
  }
}

Use Data Attributes for Configuration

export default class extends Controller {
  static values = {
    delay: { type: Number, default: 300 },
    event: { type: String, default: "input" }
  }

  connect() {
    this.element.addEventListener(this.eventValue, this.submit.bind(this))
  }

  submit() {
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => this.element.requestSubmit(), this.delayValue)
  }
}
<%= form_with data: { controller: "auto-submit", auto_submit_delay_value: 500 } %>

Compose Multiple Controllers

<div data-controller="toggle clipboard" data-toggle-open-value="false">
  <button data-action="toggle#toggle">Show</button>
  <div data-toggle-target="content" class="hidden">
    <code data-clipboard-target="source">secret-code</code>
    <button data-action="clipboard#copy">Copy</button>
  </div>
</div>

Targets and Values

Targets for Element References

export default class extends Controller {
  static targets = ["tab", "panel"]
  static values = { index: { type: Number, default: 0 } }

  select(event) { this.indexValue = this.tabTargets.indexOf(event.currentTarget) }

  indexValueChanged() {
    this.panelTargets.forEach((panel, i) => panel.classList.toggle("hidden", i !== this.indexValue))
    this.tabTargets.forEach((tab, i) => tab.setAttribute("aria-selected", i === this.indexValue))
  }
}

Action Handling

<button data-action="click->toggle#toggle">Toggle</button>
<input data-action="input->search#update focus->search#expand">
<button data-action="modal#open" data-modal-id-param="confirm-dialog">Open</button>
<input data-action="keydown.enter->form#submit keydown.escape->form#cancel">

Action Parameters

open(event) {
  const modalId = event.params.id
  document.getElementById(modalId)?.showModal()
}

Common Controller Patterns

Dropdown Controller

export default class extends Controller {
  static targets = ["menu"]
  static values = { open: Boolean }

  toggle() { this.openValue = !this.openValue }

  close(event) {
    if (!this.element.contains(event.target)) this.openValue = false
  }

  openValueChanged() {
    this.menuTarget.classList.toggle("hidden", !this.openValue)
    if (this.openValue) document.addEventListener("click", this.close.bind(this), { once: true })
  }
}

Clipboard Controller

export default class extends Controller {
  static targets = ["source", "button"]
  static values = { successMessage: { type: String, default: "Copied!" } }

  async copy() {
    const text = this.sourceTarget.value || this.sourceTarget.textContent
    await navigator.clipboard.writeText(text)
    this.showSuccess()
  }

  showSuccess() {
    const original = this.buttonTarget.textContent
    this.buttonTarget.textContent = this.successMessageValue
    setTimeout(() => this.buttonTarget.textContent = original, 2000)
  }
}

Turbo Integration

export default class extends Controller {
  connect() {
    document.addEventListener("turbo:before-visit", this.dismiss.bind(this))
    this.timeout = setTimeout(() => this.dismiss(), 5000)
  }

  disconnect() { clearTimeout(this.timeout) }
  dismiss() { this.element.remove() }
}

Anti-Patterns

Anti-Pattern Problem Solution
Creating DOM extensively Fighting Stimulus philosophy Let server render HTML
Storing state in JS State lost on navigation Use Values in HTML
Over-specific controllers Not reusable Design generic behaviors
Manual querySelector Fragile, bypasses Stimulus Use targets
Inline event handlers Unmaintainable Use data-action

Output Format

When creating Stimulus controllers, provide:

  1. Controller - Complete JavaScript implementation
  2. HTML Example - Sample markup showing usage
  3. Configuration - Available values and targets
  4. Integration - How it works with Turbo if applicable