Avoid inline JavaScript
Inline JavaScript is avoided. JavaScript is kept in external files for caching and maintainability.
- Move onclick/onmouseover handlers to external JS files
- Use addEventListener instead of inline event handlers
- Exception: critical above-the-fold JS can be inlined
- External files enable browser caching across pages
Rule Details
Avoid mixing JavaScript code with HTML markup by using external <script> files (opens in new tab), addEventListener() (opens in new tab), and data attributes so behavior stays separate from markup and compatible with a strict Content Security Policy.
Code Examples
Problems with Inline JavaScript
<!-- ❌ Bad: Inline JavaScript mixed with HTML -->
<button onclick="alert('Hello World!')">Click me</button>
<div onmouseover="this.style.background='red'" onmouseout="this.style.background='white'">
Hover me
</div>
<script>
// Inline script block
function doSomething() {
document.getElementById('myButton').addEventListener('click', function() {
alert('Button clicked!')
})
}
doSomething()
</script>
<a href="javascript:void(0)" onclick="toggleMenu()">Menu</a>
<input type="text" onchange="validateInput(this.value)">Issues This Creates
<!-- Problems with inline JavaScript: -->
<!-- 1. No caching - JavaScript downloaded with every page -->
<!-- 2. Maintenance nightmare - code scattered throughout HTML -->
<!-- 3. Security vulnerabilities - CSP violations, XSS risks -->
<!-- 4. No minification - code not optimized for production -->
<!-- 5. Hard to test - JavaScript mixed with markup -->
<!-- 6. Performance impact - blocks HTML parsing -->
<!-- 7. No reusability - same code repeated across pages -->
<!-- 8. Debugging difficulties - no source maps, line numbers -->Why It Matters
Inline JavaScript breaks browser caching, violates Content Security Policy (CSP), and creates unmaintainable spaghetti code mixing markup with behavior.
Correct External JavaScript Approach
Proper HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Clean JavaScript Implementation</title>
<!-- External JavaScript files -->
<script src="js/utils.js" defer></script>
<script src="js/components.js" defer></script>
<script src="js/main.js" defer></script>
</head>
<body>
<!-- Clean HTML with semantic classes and data attributes -->
<button class="btn btn-primary" data-action="greet">
Click me
</button>
<div class="hover-element" data-hover-color="red">
Hover me
</div>
<button class="menu-toggle" data-target="#main-menu">
Menu
</button>
<input type="text" class="validate-input" data-validation="required">
<!-- No inline JavaScript anywhere -->
</body>
</html>External JavaScript Files
// js/main.js
document.addEventListener('DOMContentLoaded', function() {
// Initialize all components
initializeButtons()
initializeHoverElements()
initializeMenuToggles()
initializeInputValidation()
})
function initializeButtons() {
const buttons = document.querySelectorAll('[data-action="greet"]')
buttons.forEach(button => {
button.addEventListener('click', function() {
showGreeting('Hello World!')
})
})
}
function initializeHoverElements() {
const hoverElements = document.querySelectorAll('.hover-element')
hoverElements.forEach(element => {
const hoverColor = element.dataset.hoverColor || 'red'
const originalColor = getComputedStyle(element).backgroundColor
element.addEventListener('mouseenter', function() {
this.style.backgroundColor = hoverColor
})
element.addEventListener('mouseleave', function() {
this.style.backgroundColor = originalColor
})
})
}
function initializeMenuToggles() {
const menuToggles = document.querySelectorAll('.menu-toggle')
menuToggles.forEach(toggle => {
toggle.addEventListener('click', function(e) {
e.preventDefault()
const target = document.querySelector(this.dataset.target)
if (target) {
target.classList.toggle('active')
}
})
})
}
function initializeInputValidation() {
const inputs = document.querySelectorAll('.validate-input')
inputs.forEach(input => {
input.addEventListener('change', function() {
validateInput(this)
})
})
}
// Utility functions
function showGreeting(message) {
// Better than alert() - create a proper notification
const notification = document.createElement('div')
notification.className = 'notification'
notification.textContent = message
document.body.appendChild(notification)
// Remove after 3 seconds
setTimeout(() => {
notification.remove()
}, 3000)
}
function validateInput(input) {
const validation = input.dataset.validation
const value = input.value.trim()
if (validation === 'required' && !value) {
showValidationError(input, 'This field is required')
return false
}
clearValidationError(input)
return true
}
function showValidationError(input, message) {
input.classList.add('error')
let errorElement = input.nextElementSibling
if (!errorElement || !errorElement.classList.contains('error-message')) {
errorElement = document.createElement('div')
errorElement.className = 'error-message'
input.parentNode.insertBefore(errorElement, input.nextSibling)
}
errorElement.textContent = message
}
function clearValidationError(input) {
input.classList.remove('error')
const errorElement = input.nextElementSibling
if (errorElement && errorElement.classList.contains('error-message')) {
errorElement.remove()
}
}Modern JavaScript Patterns
Event Delegation
// js/event-delegation.js
class EventManager {
constructor() {
this.initialize()
}
initialize() {
// Use event delegation for better performance
document.addEventListener('click', this.handleClick.bind(this))
document.addEventListener('change', this.handleChange.bind(this))
document.addEventListener('submit', this.handleSubmit.bind(this))
}
handleClick(event) {
const { target } = event
// Handle different click actions based on data attributes
if (target.dataset.action) {
this.executeAction(target.dataset.action, target, event)
}
// Handle toggle actions
if (target.classList.contains('toggle-button')) {
this.handleToggle(target, event)
}
// Handle modal triggers
if (target.dataset.modal) {
this.openModal(target.dataset.modal, event)
}
}
handleChange(event) {
const { target } = event
// Handle form validation
if (target.classList.contains('validate-on-change')) {
this.validateField(target)
}
// Handle dependent fields
if (target.dataset.affects) {
this.updateDependentFields(target)
}
}
handleSubmit(event) {
const { target } = event
if (target.classList.contains('ajax-form')) {
event.preventDefault()
this.submitFormAjax(target)
}
}
executeAction(action, element, event) {
const actions = {
'show-notification': () => this.showNotification(element.dataset.message),
'copy-to-clipboard': () => this.copyToClipboard(element.dataset.text),
'scroll-to': () => this.scrollToElement(element.dataset.target),
'toggle-theme': () => this.toggleTheme(),
'share': () => this.shareContent(element.dataset.url, element.dataset.title)
}
if (actions[action]) {
actions[action]()
}
}
showNotification(message) {
const notification = document.createElement('div')
notification.className = 'notification fade-in'
notification.innerHTML = `
<span class="notification-message">${message}</span>
<button class="notification-close">×</button>
`
document.body.appendChild(notification)
// Auto-remove after 5 seconds
setTimeout(() => {
notification.classList.add('fade-out')
setTimeout(() => notification.remove(), 300)
}, 5000)
// Handle close button
notification.querySelector('.notification-close').addEventListener('click', () => {
notification.classList.add('fade-out')
setTimeout(() => notification.remove(), 300)
})
}
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text)
this.showNotification('Copied to clipboard!')
} catch (error) {
console.error('Failed to copy text:', error)
this.showNotification('Failed to copy text')
}
}
scrollToElement(selector) {
const element = document.querySelector(selector)
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
})
}
}
toggleTheme() {
document.body.classList.toggle('dark-theme')
localStorage.setItem('theme',
document.body.classList.contains('dark-theme') ? 'dark' : 'light'
)
}
async shareContent(url, title) {
if (navigator.share) {
try {
await navigator.share({ title, url })
} catch (error) {
console.log('Share cancelled or failed:', error)
}
} else {
// Fallback to copying URL
this.copyToClipboard(url)
}
}
}
// Initialize event manager
new EventManager()Component-Based Architecture
// js/components/button.js
class Button {
constructor(element) {
this.element = element
this.initialize()
}
initialize() {
this.bindEvents()
this.setState(this.element.dataset.state || 'default')
}
bindEvents() {
this.element.addEventListener('click', this.handleClick.bind(this))
this.element.addEventListener('mouseenter', this.handleMouseEnter.bind(this))
this.element.addEventListener('mouseleave', this.handleMouseLeave.bind(this))
}
handleClick(event) {
if (this.element.disabled) {
event.preventDefault()
return
}
const action = this.element.dataset.action
if (action) {
this.executeAction(action)
}
// Emit custom event
this.element.dispatchEvent(new CustomEvent('button:click', {
detail: { action, element: this.element }
}))
}
handleMouseEnter() {
this.element.classList.add('hover')
}
handleMouseLeave() {
this.element.classList.remove('hover')
}
executeAction(action) {
switch (action) {
case 'submit':
this.submitForm()
break
case 'reset':
this.resetForm()
break
case 'toggle':
this.toggleState()
break
default:
console.warn(`Unknown button action: ${action}`)
}
}
submitForm() {
const form = this.element.closest('form')
if (form) {
form.dispatchEvent(new Event('submit', { bubbles: true }))
}
}
resetForm() {
const form = this.element.closest('form')
if (form) {
form.reset()
}
}
toggleState() {
const currentState = this.element.dataset.state
const newState = currentState === 'active' ? 'inactive' : 'active'
this.setState(newState)
}
setState(state) {
this.element.dataset.state = state
this.element.classList.remove('default', 'active', 'inactive', 'loading', 'disabled')
this.element.classList.add(state)
}
setLoading(loading = true) {
if (loading) {
this.setState('loading')
this.element.disabled = true
} else {
this.setState('default')
this.element.disabled = false
}
}
}
// Auto-initialize all buttons
document.addEventListener('DOMContentLoaded', () => {
const buttons = document.querySelectorAll('button[data-component="button"]')
buttons.forEach(button => new Button(button))
})Framework-Specific Implementations
React - Proper Event Handling
// ❌ Bad: Inline event handlers
function BadComponent() {
return (
<div>
<button onClick={() => alert('Hello!')}>
Click me
</button>
<input
onChange={(e) => {
if (e.target.value.length < 3) {
alert('Too short!')
}
}}
/>
<div
onMouseEnter={() => document.body.style.background = 'red'}
onMouseLeave={() => document.body.style.background = 'white'}
>
Hover area
</div>
</div>
)
}
// ✅ Good: Proper event handling
import { useState, useCallback, useRef } from 'react'
function GoodComponent() {
const [inputValue, setInputValue] = useState('')
const [notification, setNotification] = useState('')
const timeoutRef = useRef()
const handleButtonClick = useCallback(() => {
setNotification('Hello!')
// Clear notification after 3 seconds
clearTimeout(timeoutRef.current)
timeoutRef.current = setTimeout(() => {
setNotification('')
}, 3000)
}, [])
const handleInputChange = useCallback((event) => {
const value = event.target.value
setInputValue(value)
if (value.length < 3 && value.length > 0) {
setNotification('Input too short!')
} else {
setNotification('')
}
}, [])
const handleMouseEnter = useCallback(() => {
document.body.classList.add('highlight-mode')
}, [])
const handleMouseLeave = useCallback(() => {
document.body.classList.remove('highlight-mode')
}, [])
return (
<div className="component">
<button
className="btn btn-primary"
onClick={handleButtonClick}
type="button"
>
Click me
</button>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
className={inputValue.length < 3 && inputValue.length > 0 ? 'error' : ''}
/>
<div
className="hover-area"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
Hover area
</div>
{notification && (
<div className="notification">
{notification}
</div>
)}
</div>
)
}
export default GoodComponentVue.js - Template and Script Separation
<!-- ❌ Bad: Inline JavaScript in template -->
<template>
<div>
<button @click="alert('Hello!')">Click me</button>
<input @change="if ($event.target.value.length < 3) alert('Too short!')">
<div @mouseenter="$el.style.background = 'red'">Hover me</div>
</div>
</template>
<!-- ✅ Good: Proper method separation -->
<template>
<div class="component">
<button
class="btn btn-primary"
@click="handleButtonClick"
type="button"
>
Click me
</button>
<input
v-model="inputValue"
@input="handleInputChange"
:class="{ error: isInputTooShort }"
type="text"
/>
<div
class="hover-area"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
Hover area
</div>
<div v-if="notification" class="notification">
{{ notification }}
</div>
</div>
</template>
<script>
export default {
name: 'GoodComponent',
data() {
return {
inputValue: '',
notification: '',
notificationTimeout: null
}
},
computed: {
isInputTooShort() {
return this.inputValue.length < 3 && this.inputValue.length > 0
}
},
methods: {
handleButtonClick() {
this.showNotification('Hello!')
},
handleInputChange() {
if (this.isInputTooShort) {
this.showNotification('Input too short!')
} else {
this.clearNotification()
}
},
handleMouseEnter() {
document.body.classList.add('highlight-mode')
},
handleMouseLeave() {
document.body.classList.remove('highlight-mode')
},
showNotification(message) {
this.notification = message
clearTimeout(this.notificationTimeout)
this.notificationTimeout = setTimeout(() => {
this.clearNotification()
}, 3000)
},
clearNotification() {
this.notification = ''
if (this.notificationTimeout) {
clearTimeout(this.notificationTimeout)
this.notificationTimeout = null
}
}
},
beforeUnmount() {
// Cleanup timeouts
if (this.notificationTimeout) {
clearTimeout(this.notificationTimeout)
}
}
}
</script>
<style scoped>
.component {
padding: 1rem;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary {
background: #007bff;
color: white;
}
.error {
border-color: #dc3545;
}
.notification {
margin-top: 1rem;
padding: 0.75rem;
background: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 4px;
color: #155724;
}
.hover-area {
padding: 2rem;
border: 2px dashed #ccc;
text-align: center;
margin-top: 1rem;
}
</style>Angular - Component Architecture
// ❌ Bad: Inline event handling
@Component({
template: `
<button (click)="alert('Hello!')">Click me</button>
<input (change)="$event.target.value.length < 3 ? alert('Too short!') : null">
`
})
export class BadComponent {}
// ✅ Good: Proper component structure
import { Component, OnDestroy } from '@angular/core'
@Component({
selector: 'app-good-component',
template: `
<div class="component">
<button
class="btn btn-primary"
(click)="handleButtonClick()"
type="button"
>
Click me
</button>
<input
[(ngModel)]="inputValue"
(input)="handleInputChange()"
[class.error]="isInputTooShort"
type="text"
/>
<div
class="hover-area"
(mouseenter)="handleMouseEnter()"
(mouseleave)="handleMouseLeave()"
>
Hover area
</div>
<div *ngIf="notification" class="notification">
{{ notification }}
</div>
</div>
`,
styleUrls: ['./good-component.component.css']
})
export class GoodComponent implements OnDestroy {
inputValue = ''
notification = ''
private notificationTimeout?: number
get isInputTooShort(): boolean {
return this.inputValue.length < 3 && this.inputValue.length > 0
}
handleButtonClick(): void {
this.showNotification('Hello!')
}
handleInputChange(): void {
if (this.isInputTooShort) {
this.showNotification('Input too short!')
} else {
this.clearNotification()
}
}
handleMouseEnter(): void {
document.body.classList.add('highlight-mode')
}
handleMouseLeave(): void {
document.body.classList.remove('highlight-mode')
}
private showNotification(message: string): void {
this.notification = message
if (this.notificationTimeout) {
clearTimeout(this.notificationTimeout)
}
this.notificationTimeout = window.setTimeout(() => {
this.clearNotification()
}, 3000)
}
private clearNotification(): void {
this.notification = ''
if (this.notificationTimeout) {
clearTimeout(this.notificationTimeout)
this.notificationTimeout = undefined
}
}
ngOnDestroy(): void {
if (this.notificationTimeout) {
clearTimeout(this.notificationTimeout)
}
}
}Content Security Policy (CSP) Compliance
CSP Configuration
<!-- Strict CSP that prevents inline JavaScript -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-eval';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
">CSP-Compliant JavaScript
// js/csp-compliant.js
// No eval(), no Function(), no inline event handlers
class CSPCompliantApp {
constructor() {
this.initialize()
}
initialize() {
// Use proper event listeners instead of inline handlers
this.bindEvents()
this.loadConfiguration()
}
bindEvents() {
// Event delegation for dynamically added elements
document.addEventListener('click', this.handleGlobalClick.bind(this))
document.addEventListener('DOMContentLoaded', this.handleDOMReady.bind(this))
}
handleGlobalClick(event) {
const action = event.target.dataset.action
if (action && this.actions[action]) {
this.actions[action](event.target, event)
}
}
// Define actions as methods instead of eval() or Function()
actions = {
showAlert: (element) => {
this.showAlert(element.dataset.message || 'Default message')
},
toggleClass: (element) => {
const className = element.dataset.toggleClass
const target = element.dataset.target
? document.querySelector(element.dataset.target)
: element
if (target && className) {
target.classList.toggle(className)
}
},
submitForm: (element) => {
const form = element.closest('form')
if (form) {
this.handleFormSubmission(form)
}
}
}
showAlert(message) {
// Use proper DOM manipulation instead of alert()
const alertDiv = document.createElement('div')
alertDiv.className = 'alert'
alertDiv.textContent = message
document.body.appendChild(alertDiv)
setTimeout(() => {
alertDiv.remove()
}, 3000)
}
async loadConfiguration() {
// Load configuration from external source instead of inline
try {
const response = await fetch('/api/config')
const config = await response.json()
this.applyConfiguration(config)
} catch (error) {
console.error('Failed to load configuration:', error)
this.applyDefaultConfiguration()
}
}
applyConfiguration(config) {
// Apply configuration without using eval()
if (config.theme) {
document.body.className = `theme-${config.theme}`
}
if (config.features) {
config.features.forEach(feature => {
document.body.classList.add(`feature-${feature}`)
})
}
}
applyDefaultConfiguration() {
this.applyConfiguration({
theme: 'default',
features: ['basic']
})
}
}
// Initialize application
new CSPCompliantApp()Performance Optimization
Lazy Loading JavaScript
// js/lazy-loader.js
class LazyJavaScriptLoader {
constructor() {
this.loadedModules = new Set()
this.observers = new Map()
}
// Load JavaScript modules on demand
async loadModule(moduleName, condition = () => true) {
if (this.loadedModules.has(moduleName) || !condition()) {
return
}
try {
const module = await import(`./modules/${moduleName}.js`)
this.loadedModules.add(moduleName)
if (module.initialize) {
module.initialize()
}
return module
} catch (error) {
console.error(`Failed to load module ${moduleName}:`, error)
}
}
// Load modules when elements come into view
observeElement(element, moduleName) {
if (!('IntersectionObserver' in window)) {
// Fallback: load immediately
this.loadModule(moduleName)
return
}
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadModule(moduleName)
observer.unobserve(entry.target)
}
})
}, { threshold: 0.1 })
observer.observe(element)
this.observers.set(element, observer)
}
// Load modules based on user interaction
loadOnInteraction(selector, moduleName, eventType = 'click') {
const elements = document.querySelectorAll(selector)
elements.forEach(element => {
const handler = () => {
this.loadModule(moduleName)
element.removeEventListener(eventType, handler)
}
element.addEventListener(eventType, handler, { once: true })
})
}
// Load modules based on media queries
loadOnMediaQuery(query, moduleName) {
const mediaQuery = window.matchMedia(query)
const handler = (mq) => {
if (mq.matches) {
this.loadModule(moduleName)
}
}
handler(mediaQuery) // Check initial state
mediaQuery.addEventListener('change', handler)
}
}
// Usage
const lazyLoader = new LazyJavaScriptLoader()
// Load chart library when chart container is visible
document.addEventListener('DOMContentLoaded', () => {
const chartContainer = document.querySelector('.chart-container')
if (chartContainer) {
lazyLoader.observeElement(chartContainer, 'charts')
}
// Load modal functionality on first modal trigger click
lazyLoader.loadOnInteraction('[data-modal]', 'modals')
// Load mobile navigation on mobile devices
lazyLoader.loadOnMediaQuery('(max-width: 768px)', 'mobile-nav')
})Best Practices
Build around external scripts, data-* attributes (opens in new tab), and event listeners so the HTML stays semantic even when the JavaScript needs to evolve independently.
- External Files: Always use external JavaScript files instead of inline scripts
- Event Delegation: Use event delegation for better performance and maintainability
- CSP Compliance: Ensure code works with strict Content Security Policies
- Separation of Concerns: Keep HTML, CSS, and JavaScript separate
- Performance: Implement lazy loading for non-critical JavaScript
- Accessibility: Use proper event handling for keyboard and screen reader support
- Error Handling: Implement proper error handling and fallbacks
- Modern Patterns: Use modern JavaScript patterns and APIs
- Testing: Write testable code by separating logic from DOM manipulation
- Documentation: Document complex interactions and component behaviors
Migration Strategy
Converting Inline to External JavaScript
// scripts/inline-to-external-converter.js
const fs = require('fs')
const cheerio = require('cheerio')
class InlineToExternalConverter {
constructor() {
this.extractedScripts = []
this.inlineHandlers = []
this.scriptCounter = 0
}
convertFile(filePath) {
const content = fs.readFileSync(filePath, 'utf8')
const $ = cheerio.load(content)
// Extract inline scripts
$('script:not([src])').each((index, script) => {
const scriptContent = $(script).html()
if (scriptContent.trim()) {
const scriptName = `extracted-script-${this.scriptCounter++}.js`
this.extractedScripts.push({
name: scriptName,
content: scriptContent
})
// Replace with external script reference
$(script).attr('src', `js/${scriptName}`)
$(script).html('')
}
})
// Extract inline event handlers
$('*').each((index, element) => {
const $element = $(element)
const attributes = element.attribs || {}
Object.keys(attributes).forEach(attr => {
if (attr.startsWith('on') && attributes[attr].includes('(')) {
const eventType = attr.substring(2) // Remove 'on' prefix
const handlerCode = attributes[attr]
const elementId = $element.attr('id') || `element-${index}`
// Add ID if not present
if (!$element.attr('id')) {
$element.attr('id', elementId)
}
// Remove inline handler
$element.removeAttr(attr)
// Store handler for external script
this.inlineHandlers.push({
elementId,
eventType,
handlerCode
})
}
})
})
// Write updated HTML
fs.writeFileSync(filePath, $.html())
return {
extractedScripts: this.extractedScripts,
inlineHandlers: this.inlineHandlers
}
}
generateExternalScripts(outputDir) {
// Create directory if it doesn't exist
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true })
}
// Write extracted scripts
this.extractedScripts.forEach(script => {
fs.writeFileSync(
`${outputDir}/${script.name}`,
script.content
)
})
// Generate handlers script
if (this.inlineHandlers.length > 0) {
const handlersScript = this.generateHandlersScript()
fs.writeFileSync(
`${outputDir}/event-handlers.js`,
handlersScript
)
}
}
generateHandlersScript() {
let script = '// Auto-generated event handlers\n\n'
script += 'document.addEventListener("DOMContentLoaded", function() {\n'
this.inlineHandlers.forEach(handler => {
script += ` const element${handler.elementId.replace(/[^a-zA-Z0-9]/g, '')} = document.getElementById('${handler.elementId}');\n`
script += ` if (element${handler.elementId.replace(/[^a-zA-Z0-9]/g, '')}) {\n`
script += ` element${handler.elementId.replace(/[^a-zA-Z0-9]/g, '')}.addEventListener('${handler.eventType}', function(event) {\n`
script += ` ${handler.handlerCode}\n`
script += ` });\n`
script += ` }\n\n`
})
script += '});\n'
return script
}
}
// Usage
const converter = new InlineToExternalConverter()
const result = converter.convertFile('index.html')
converter.generateExternalScripts('./js')
console.log(`Extracted ${result.extractedScripts.length} inline scripts`)
console.log(`Converted ${result.inlineHandlers.length} inline event handlers`)Remember that separating JavaScript from HTML is not just about code organization—it's about creating maintainable, secure, and performant web applications that can scale effectively.
Verification
- Verify the behavior in the browser after the code change, not only in static analysis.
- Inspect DevTools Network or Performance panels when the rule affects loading or execution order.
- Test the primary user flow and one edge case triggered by the changed script path.
- Confirm the code still behaves correctly when the feature is delayed, lazy-loaded, or fails.
Use with AI
Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.
Check
Verify implementation
Verify that JavaScript code is not mixed with HTML markup using external script files and proper separation of concerns for better maintainability and performance.
Fix
Auto-fix issues
Move inline JavaScript to external files, implement proper event handling, and use modern JavaScript patterns for DOM interaction.
Explain
Learn more
Explain why separating JavaScript from HTML improves maintainability, enables caching, reduces security risks, and follows best practices for separation of concerns.
Review
Code review
Review scripts, client components, and browser execution paths related to Avoid inline JavaScript. Flag exact imports, event handlers, runtime side effects, or blocking operations that violate the rule, and state how the change should be verified in the browser.