Thank you for your interest in contributing to GameInputJS! This project is primarily maintained by one developer, but I've made it open-source for two reasons:
- So that others may learn and use the techniques employed here
- To enable community contributions for features, fixes, and especially new gamepad mappings
This guide will help you contribute effectively, especially if you want to add support for a new gamepad model.
- Getting Started
- Development Workflow
- Adding a New Gamepad Mapping
- Code Style and Conventions
- Testing
- Submitting Changes
- Code of Conduct
- Node.js: >= 24.13.0
- npm: >= 11.0.0
- One or more gamepads
Fork the repository on GitHub and clone it locally.
npm install# Run linter
npm run lint
# Run tests
npm test
# Build the project
npm run build
# Serve demo locally
npm run serveIf everything passes, you're ready to start developing!
-
Create a new branch for your changes:
git checkout -b feature/your-feature-name
-
Make your changes
-
Run linter and fix issues:
npm run lint-fix
-
Run tests:
npm test -
Test your changes locally:
npm run serve # Visit http://localhost:3000 with your gamepad -
Commit your changes:
git add . git commit -m "Description of your changes"
-
Push to your fork:
git push origin feature/your-feature-name
-
Open a Pull Request on GitHub
One of the most valuable contributions is adding support for new gamepad models! Here's how:
Connect your gamepad and open the browser console:
// Get your gamepad's information
const gamepad = navigator.getGamepads()[0]
console.log('ID:', gamepad.id)
console.log('Mapping:', gamepad.mapping)
console.log('Buttons:', gamepad.buttons.length)
console.log('Axes:', gamepad.axes.length)Example output:
ID: "057e-2009-Nintendo Switch Pro Controller"
^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
VID PID Name
What you need:
- Vendor ID (VID): First 4 hex digits (e.g.,
057e) - Product ID (PID): Next 4 hex digits (e.g.,
2009) - Device Name: The text after the IDs
- Operating System: Windows, Linux, macOS, etc.
- Browser: Chrome, Firefox, Safari, Edge
Use one of the many online gamepad testers (such as Gamepad Tester or HTML5 Gamepad Tester) to identify each button and axis.
Test each input and record:
- Face buttons (A/B/X/Y or Cross/Circle/Square/Triangle)
- D-pad (up, down, left, right)
- Shoulder buttons (L1/LB, R1/RB)
- Triggers (L2/LT, R2/RT) - note if analog or digital
- Analog sticks (left/right, X/Y axes, and click buttons)
- Center buttons (Start, Select, Menu, View, Home)
GameInputJS uses three primary button naming schemes:
| Schema | Style | Primary Button Names | Used By |
|---|---|---|---|
| Hedgehog | Xbox | A, B, X, Y | Xbox controllers |
| Plumber | Nintendo | B, A, Y, X | Nintendo controllers |
| Xross | PlayStation | Cross, Circle, Square, Triangle | PlayStation controllers |
Choose the schema that matches your gamepad's button layout.
Open /src/gameinput-models.js and add your gamepad to the GameInputModels array:
new GameInputModel(
GameInputSchema.Hedgehog, // Choose: Hedgehog, Plumber, or Xross
'generic', // Icon name (see /img/ folder or use 'generic')
'057e-2009-Nintendo Switch Pro Controller', // Exact ID from Step 1
'Linux', // Operating System (or undefined for all)
StandardGamepadMapping // Use StandardGamepadMapping if standard layout
)If the button mapping is non-standard, you'll need to create a custom mapping:
new GameInputModel(
GameInputSchema.Hedgehog,
'generic',
'Custom Gamepad ID',
'Windows',
StandardGamepadMapping.variant({
face: new GamepadFaceMapping(0, 1, 2, 3), // bottom, right, left, top
dpad: new GamepadDirectionsMapping(12, 13, 14, 15), // up, down, left, right
shoulder: new GamepadLRMapping(4, 5), // left, right
trigger: new GamepadLRMapping(6, 7), // left, right
leftStick: new GamepadAnalogStickMapping(0, 1, 10), // xAxis, yAxis, button
rightStick: new GamepadAnalogStickMapping(2, 3, 11), // xAxis, yAxis, button
center: new GamepadCenterMapping(8, 9) // select, start
})
)For analog triggers as axes, use AxisAsButton:
trigger: new GamepadLRMapping(
new AxisAsButton('+', 2), // Left trigger on axis 2 (positive direction)
new AxisAsButton('+', 5) // Right trigger on axis 5 (positive direction)
)Check the /img/ directory for existing icons:
generic.png- Generic gamepadxbox360.png,xboxone.png- Xbox controllersds3.png,ds4.png,ds5.png- PlayStation controllerssnes.png,n64.png,joycons.png- Nintendo controllers- And more...
If you have a unique icon for your gamepad, you can add it to /img/ and reference it in your GameInputModel. Icons must be human-created and never AI-generated.
-
Build and serve the demo:
npm run serve
-
Visit http://localhost:3000 and connect your gamepad
-
Verify:
- All buttons register correctly
- D-pad works in all directions
- Both analog sticks work (including clicks)
- Triggers respond correctly
- The correct icon appears
-
Test in multiple browsers if possible (Chrome, Firefox, Safari, Edge)
If you're comfortable with Jest, consider adding a test to verify your mapping:
// In src/gameinput-models.test.js or similar
describe('Custom Gamepad Model', () => {
it('should match the correct gamepad ID', () => {
const model = GameInputModels.find(m =>
m.id === 'Your-Gamepad-ID'
)
expect(model).toBeDefined()
expect(model.schema).toBe(GameInputSchema.Hedgehog)
expect(model.iconName).toBe('generic')
})
})- Module System: Use ES modules (
import/export) exclusively - Indentation: 4 spaces (enforced by EditorConfig)
- Quotes: Single quotes for strings
- Semicolons: Not used (enforced by eslint-config-standard)
- Line Endings: LF (Unix style)
- Naming: camelCase for variables/functions, PascalCase for classes
- File Names: kebab-case with
.jsextension
All public APIs must have JSDoc comments. This project uses JSDoc as its type system instead of TypeScript.
/**
* Brief description of the function
* @param {string} name - Parameter description
* @param {number} [optional] - Optional parameter (note the brackets)
* @returns {boolean} Return value description
* @throws {Error} When invalid input provided
*/
function exampleFunction(name, optional) {
// implementation
}/**
* Example class description
* @class
*/
class ExampleClass {
/**
* Property description
* @type {string}
*/
propertyName
/**
* Constructor description
* @param {Object} options - Configuration object
* @param {string} options.name - Name property
*/
constructor(options) {
this.propertyName = options.name
}
/**
* Method description
* @param {number} value - Input value
* @returns {number} Processed value
*/
methodName(value) {
return value * 2
}
}- Run
npm run lintbefore committing - Run
npm run lint-fixto auto-fix most issues - The project extends
eslint-config-standard - Don't disable linter warnings without good reason
The .editorconfig file enforces formatting rules:
- 4 spaces for JavaScript/HTML/CSS
- 2 spaces for YAML
- UTF-8 encoding
- LF line endings
- Trim trailing whitespace
- Insert final newline
Make sure your editor supports EditorConfig.
# Run all tests
npm test
# Run with coverage
npm run test:coverageWhen adding a new gamepad, manually test:
- All face buttons (A/B/X/Y or equivalent)
- D-pad in all four directions
- Both analog sticks (X/Y movement)
- Both stick clicks (L3/R3 or LS/RS)
- Shoulder buttons (L1/R1 or LB/RB)
- Triggers (L2/R2 or LT/RT)
- Center buttons (Start, Select, Menu, View, Home)
- Rumble/vibration if supported
- Test in Chrome
- Test in Firefox (optional but recommended)
- Add unit tests for new features
- Run full test suite:
npm test - Ensure tests pass in CI
- Update JSDoc if API changed
- Run
npm run type-checkto verify JSDoc types - Test in multiple browsers if affecting core gamepad logic
Tests use Jest with jsdom environment. Follow existing patterns:
import { describe, it, expect } from '@jest/globals'
import { MyClass } from './my-class.js'
describe('MyClass', () => {
describe('methodName', () => {
it('should do something specific', () => {
const instance = new MyClass({ value: 'test' })
const result = instance.methodName()
expect(result).toBe('expected')
})
it('should handle edge case', () => {
const instance = new MyClass({ value: '' })
expect(() => instance.methodName()).toThrow()
})
})
})- Run
npm run lint-fixand fix any remaining issues - Run
npm test- all tests must pass - Test your changes in the demo (
npm run serve) - For gamepad mappings: test on real hardware
- Update JSDoc if you changed any public APIs
- Update documentation if needed
- Commit with a clear, descriptive message
- Reference any related issue numbers
-
Solve an open issue (ideally) - check the issue tracker
-
Keep PRs focused - one feature or fix per PR
-
Write a clear description:
- What does this PR do?
- How was it tested?
- Any breaking changes?
- Screenshots/videos if UI-related
-
Use the PR template - fill out all sections
-
Be patient - the maintainer will review when available
-
Respond to feedback - address review comments promptly
-
Final decision - the maintainer reserves the right to accept or reject any PR
- Use present tense ("Add feature" not "Added feature")
- Use imperative mood ("Move cursor to..." not "Moves cursor to...")
- First line is a brief summary (50 chars or less)
- Reference issues and PRs when relevant
- For gamepad mappings: include controller name
Examples:
Add support for Xbox Elite Controller Series 2
Fix D-pad mapping on Nintendo Switch Pro Controller
Update JSDoc for GameInput constructor options
-
Zero Runtime Dependencies - This project has no runtime dependencies by design. All functionality is self-contained.
-
No Build Transformation - Source files are distributed as-is (ES modules). No Babel, no TypeScript compilation.
-
Browser-Only - This is a client-side library. No Node.js runtime code.
-
Backward Compatibility - Maintain backward compatibility with existing APIs.
NEVER:
- Add runtime dependencies
- Add build steps that transform source code
- Remove or modify JSDoc comments without updating them
- Break backward compatibility without discussion
- Disable linter warnings without justification
ALWAYS:
- Use ES modules with
.jsextensions in imports - Maintain JSDoc for all public APIs
- Test with real hardware for gamepad changes
- Run linter and tests before committing
ASK FIRST:
- Before adding new gamepad models (or submit a PR)
- Before changing the button schema system
- Before making breaking changes
- Be respectful and inclusive
- Welcome newcomers and help them learn
- Focus on what's best for the project
- Accept constructive criticism gracefully
- Use a neutral, objective tone in code and comments
- Harassment or discrimination of any kind
- Trolling, insulting, or derogatory comments
- Publishing others' private information
- Other conduct inappropriate in a professional setting
The project maintainer has the final say on all contributions and conduct issues. Inappropriate behavior may result in removal from the project community.
- Bug reports: Open an issue
- Feature requests: Open an issue
- Questions: Check existing issues or open a new one
- Gamepad testing help: Include your gamepad ID and browser info in your issue
- README.md - Project overview and usage
- W3C Gamepad API Spec
- MDN Gamepad API Documentation
- Project Demo - Example usage
Thank you for contributing to GameInputJS! 🎮
- Sam (Project Maintainer)