Practices and Specific ES Features > ime-safe-form
Prevents accidental form submission during IME composition by requiring e.isComposing guard or form submit event.
eslint-plugin-ime-safe-form
ESLint plugin to enforce IME-safe form submission for users who type with an IME (Input Method Editor).
Quick Start
npm install --save-dev eslint-plugin-ime-safe-form
// eslint.config.js (ESLint 9)
import imeSafeForm from 'eslint-plugin-ime-safe-form';
export default [imeSafeForm.configs.recommended];
For ESLint 8 / .eslintrc setup and advanced configuration, see Usage.
Try it in the playground.
Why
When checking for the Enter key in keydown/keyup handlers to submit a form, users typing with an IME experience broken input: pressing Enter to confirm IME candidates accidentally triggers form submission before the composition is complete.
There are three correct approaches:
// ✅ Option 1: use the form's submit event (fires after composition ends — no guard needed)
form.addEventListener('submit', (e) => {
e.preventDefault();
submit();
});
// ✅ Option 2: require a modifier key — IME cannot be composing while Ctrl/Meta/Shift/Alt is held
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) submit();
});
// ✅ Option 3: guard with e.isComposing + e.keyCode === 229 (covers Safari)
input.addEventListener('keydown', (e) => {
if (e.isComposing || e.keyCode === 229) return;
if (e.key === 'Enter') submit();
});
// ❌ Bad — breaks IME input
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') submit();
});
keypress is prohibited entirely as it is deprecated.
Safari note: In Safari, compositionend fires before keydown, so e.isComposing is false when Enter confirms IME. The e.keyCode === 229 check covers this gap. To require only e.isComposing (if Safari support is not needed), set { checkKeyCodeForSafari: false } in the rule options.
Custom guard functions: If you extract the isComposing check into a shared helper, use the guardFunctions option to register the function name so the rule recognises it as an IME guard:
const guardIsComposing = (e) => e.isComposing || e.keyCode === 229;
// eslint.config.js
rules: { 'ime-safe-form/require-ime-safe-submit': ['warn', { guardFunctions: ['guardIsComposing'] }] }
Installation
npm install --save-dev eslint-plugin-ime-safe-form
Usage
Flat config (eslint.config.js, ESLint 9)
import imeSafeForm from 'eslint-plugin-ime-safe-form';
export default [
imeSafeForm.configs.recommended,
];
Note: The recommended config sets the rule severity to "warn". To treat violations as errors, configure the rule manually:
rules: { 'ime-safe-form/require-ime-safe-submit': 'error' }
Manual configuration
import imeSafeForm from 'eslint-plugin-ime-safe-form';
export default [
{
plugins: { 'ime-safe-form': imeSafeForm },
rules: {
'ime-safe-form/require-ime-safe-submit': 'warn',
},
},
];
Legacy config (.eslintrc.js, ESLint 8)
module.exports = {
plugins: ['ime-safe-form'],
extends: ['plugin:ime-safe-form/recommended:legacy'],
};
Or manually:
module.exports = {
plugins: ['ime-safe-form'],
rules: {
'ime-safe-form/require-ime-safe-submit': 'warn',
},
};
TypeScript / TSX
Install @typescript-eslint/parser and set it as the parser for TypeScript files:
// eslint.config.js (ESLint 9)
import imeSafeForm from 'eslint-plugin-ime-safe-form';
import tsParser from '@typescript-eslint/parser';
export default [
{
files: ['**/*.{ts,tsx}'],
languageOptions: { parser: tsParser },
},
imeSafeForm.configs.recommended,
];
// .eslintrc.js (ESLint 8)
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['ime-safe-form'],
extends: ['plugin:ime-safe-form/recommended:legacy'],
};
Rules
| Rule | Description | Recommended |
|---|---|---|
require-ime-safe-submit |
Require IME-safe form submission (isComposing guard or form submit event) | ✅ |
Detected patterns
element.addEventListener('keydown' \| 'keyup', handler)where handler checkse.key === 'Enter',e.code === 'Enter',e.keyCode === 13, ore.which === 13without ane.isComposingguard or a modifier key condition (e.ctrlKey,e.metaKey,e.shiftKey,e.altKey)element.addEventListener('keypress', handler)where handler checks for Enter — always flagged (keypressis deprecated)element.onkeydown/element.onkeyup/element.onkeypressassignments- JSX
onKeyDown/onKeyUp/onKeyPressprops switch(e.key) { case 'Enter': ... }and equivalents usinge.code,e.keyCode, ore.which
Development
# Install dependencies
npm install
# Type-check
npm run typecheck
# Run tests (no build step needed)
npm test
# Build for publishing
npm run build
Project structure
src/
index.ts # Plugin entry point
rules/
require-ime-safe-submit.ts
tests/
require-ime-safe-submit.test.ts
docs/
rules/
require-ime-safe-submit.md
dist/ # Built output (generated by npm run build)
License
MIT © Hiroya Uga