Create a new Stimulus component package under components/<name>/ following repo conventions
When the user asks to create a new component (or "add a component"), create a full component package under components/<name>/ following this repo's conventions.
my-controller, auto-submit).MyController, AutoSubmit).data-controller="my-controller").stimulus-<name> (e.g. stimulus-my-controller).@stimulus-components/<name>.If the user gives a single name (e.g. "tabs"), derive: name = tabs, class = Tabs, identifier = tabs, fileName = .
stimulus-tabsCreate components/<name>/ with these files.
components/<name>/src/index.tsController from @hotwired/stimulus.Tabs for tabs).declare for TypeScript typings of any static targets and static values.static targets = ["..."] and/or static values = { ... } as needed.connect() / disconnect() for setup/teardown; use initialize() when binding methods (e.g. for event handlers).stimulus-use and use it like in dropdown; otherwise depend only on @hotwired/stimulus like character-counter.Minimal template (no extra deps):
import { Controller } from "@hotwired/stimulus"
export default class<PascalName> extends Controller {
static targets = [] // e.g. ["panel"]
static values = {} // e.g. { open: Boolean } if needed
connect(): void {
// setup
}
disconnect(): void {
// teardown
}
}
components/<name>/package.jsonname: "@stimulus-components/<name>"version: "1.0.0"description: One line describing the controller.keywords: Include "stimulus", "stimulusjs", "stimulus controller", and the component name.repository, bugs, author, license, homepage, private, publishConfig from components/character-counter/package.json.main: "dist/stimulus-<name>.umd.js"module: "dist/stimulus-<name>.mjs"types: "dist/types/index.d.ts"scripts: "types": "tsc --noEmit false --declaration true --emitDeclarationOnly true --outDir dist/types", "dev": "vite", "build": "vite build && pnpm run types", "version": "pnpm run build""@hotwired/stimulus": "^3""stimulus-use": "^0.52.3" for transition/use APIs).components/<name>/vite.config.mtsentry: resolve(__dirname, "src/index.ts")name: Stimulus<PascalName> (e.g. StimulusTabs)fileName: stimulus-<name>external and output.globals: Include @hotwired/stimulus; add stimulus-use (and globals) only if the component uses it.Use components/character-counter/vite.config.mts as base when there are no extra deps; use components/dropdown/vite.config.mts when using stimulus-use.
components/<name>/tsconfig.json{ "extends": "../../tsconfig.json" }
components/<name>/index.htmlStimulus <PascalName>.../app.css, Application from @hotwired/stimulus, and the controller from ./src/index."<name>" (e.g. "tabs").data-controller="<name>" and the right targets/values.components/<name>/README.mdStimulus <PascalName>.https://www.stimulus-components.com/docs/stimulus-<name>/.components/dropdown/README.md).components/<name>/CHANGELOG.md## [Unreleased] and ## 1.0.0 - <date> with “### Added” and “- Initial release.” or similar."../../../utils" (relative to repo root), e.g. import { debounce } from "../../../utils".components/<name>/spec/index.test.ts.describe, it, beforeEach, expect.Application.start(), register the controller with the kebab-case name, then set document.body.innerHTML with the right data-controller, targets, and values.components/character-counter/spec/index.test.ts for structure.Add the component to the documentation site so it appears in the sidebar and has a doc page with a live demo.
docs/package.json: Add to dependencies: "@stimulus-components/<name>": "workspace:*" (alphabetically with other @stimulus-components/*).docs/plugins/stimulus.client.ts:
import <PascalName> from "@stimulus-components/<name>/src".application.register("<name>", <PascalName>) (e.g. application.register("speech-recognition", SpeechRecognition)).docs/content/docs/stimulus-<name>.md: Create a doc page with:
title, description, package: "<name>", packagePath: "@stimulus-components/<name>".:installation-block{:package="package" :packagePath="packagePath"}), optional alert, Example (:<name>), Usage (HTML example in ::code-block), Configuration table if needed, Extending Controller.queryContent("docs").sort({ title: 1 }), so the new file is picked up automatically. The URL is /docs/stimulus-<name>.Use docs/content/docs/stimulus-character-counter.md or docs/content/docs/stimulus-speech-recognition.md as a template.
docs/components/content/Demo/<PascalName>.vue:
<Block title="..."> wrapper and put inside it a div with data-controller="<name>" and the appropriate targets/values/actions.:<name> (kebab-case; Nuxt Content resolves it to the Demo component).package.json name is @stimulus-components/<name> and build outputs stimulus-<name>.mjs / stimulus-<name>.umd.js.vite.config.mts has correct name, fileName, and externals/globals..prettierrc).pnpm install, pnpm run lint, pnpm run test.docs/package.json, docs/plugins/stimulus.client.ts, docs/content/docs/stimulus-<name>.md, docs/components/content/Demo/<PascalName>.vue.| Concept | Example (name: tabs) |
|---|---|
| Folder | components/tabs/ |
| Package name | @stimulus-components/tabs |
| Controller id | tabs (data-controller="tabs") |
| Class name | Tabs |
| Vite lib name | StimulusTabs |
| File name | stimulus-tabs |