Overview
Unlike standard form inputs (<input> and <textarea>), contenteditable elements have limited support for HTML attributes. Many attributes that work on form inputs are either ignored, partially supported, or behave inconsistently across browsers.
Key Points
- No Native Validation: Attributes like
required,pattern, andmaxlengthare not supported - Limited Mobile Support: Mobile-specific attributes like
inputmodeandenterkeyhintoften don't work - Browser Inconsistencies: Even supported attributes may behave differently across browsers
- Manual Implementation Required: Many features must be implemented manually using JavaScript
Supported Attributes
These attributes generally work on contenteditable elements, though behavior may vary across browsers.
spellcheck
The spellcheck attribute controls browser spellchecking. It works on contenteditable, but may interfere with IME composition and editing flow.
<div contenteditable spellcheck="true">
<!-- Spellcheck enabled -->
</div>
<div contenteditable spellcheck="false">
<!-- Spellcheck disabled -->
</div>⚠️ Spellcheck Interference
- Spellcheck may interfere with IME composition - suggestions may appear during composition
- Accepting spellcheck suggestions may cause caret to jump unexpectedly
- Spellcheck UI may overlap with content during editing
- Consider disabling spellcheck during composition (see code example below)
// Disable spellcheck during IME composition
const editor = document.querySelector('[contenteditable]');
let isComposing = false;
editor.addEventListener('compositionstart', () => {
isComposing = true;
editor.setAttribute('spellcheck', 'false');
});
editor.addEventListener('compositionend', () => {
isComposing = false;
editor.setAttribute('spellcheck', 'true');
});lang
The lang attribute specifies the language of the content. It may affect spellcheck language in some browsers, but behavior is inconsistent.
<div contenteditable lang="en" spellcheck="true">
<!-- English content -->
</div>
<div contenteditable lang="fr" spellcheck="true">
<!-- French content -->
</div>⚠️ lang May Not Affect Spellcheck
In Safari, the lang attribute does not affect spellcheck language. Spellcheck always uses the browser's default language, regardless of the lang value.
dir
The dir attribute controls text direction (ltr or rtl). It works on contenteditable, but changing it dynamically during editing may cause issues.
<div contenteditable dir="ltr">
<!-- Left-to-right text -->
</div>
<div contenteditable dir="rtl">
<!-- Right-to-left text -->
</div>⚠️ Dynamic dir Changes
In Firefox, changing the dir attribute during active editing may not take effect immediately. The caret position may be incorrect, and text flow may not update properly. Save and restore selection when changing direction programmatically.
// Handle dir changes during editing
const editor = document.querySelector('[contenteditable]');
function setDirection(dir) {
// Save selection
const selection = window.getSelection();
const range = selection.rangeCount > 0 ? selection.getRangeAt(0).cloneRange() : null;
// Change direction
editor.setAttribute('dir', dir);
// Restore selection after a brief delay
if (range) {
requestAnimationFrame(() => {
selection.removeAllRanges();
selection.addRange(range);
});
}
}Unsupported Attributes
These attributes are ignored by browsers on contenteditable elements. You must implement the functionality manually using JavaScript.
autofocus
The autofocus attribute does not work on contenteditable. Use JavaScript to focus the element on page load.
// Implement autofocus for contenteditable
const editor = document.querySelector('[contenteditable]');
// Option 1: On page load
window.addEventListener('load', () => {
editor.focus();
});
// Option 2: Using requestAnimationFrame (better timing)
requestAnimationFrame(() => {
editor.focus();
});
// Option 3: Using DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => {
editor.focus();
});maxlength
The maxlength attribute is not supported. Implement length limiting manually using beforeinput events.
// Implement maxlength manually
const editor = document.querySelector('[contenteditable]');
const maxLength = 100;
editor.addEventListener('beforeinput', (e) => {
if (e.inputType === 'insertText' || e.inputType === 'insertCompositionText') {
const currentLength = editor.textContent.length;
const newText = e.data || '';
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const selectedLength = range.toString().length;
const newLength = currentLength - selectedLength + newText.length;
if (newLength > maxLength) {
e.preventDefault();
// Optionally show warning to user
}
}
}
});required
The required attribute does not trigger form validation. Implement validation manually.
// Manual required validation
const editor = document.querySelector('[contenteditable]');
const form = editor.closest('form');
form.addEventListener('submit', (e) => {
if (editor.textContent.trim() === '') {
e.preventDefault();
// Show validation error
editor.setAttribute('aria-invalid', 'true');
}
});pattern
The pattern attribute does not validate content. Implement pattern validation manually.
// Manual pattern validation
const editor = document.querySelector('[contenteditable]');
const pattern = /^[0-9]+$/; // Numbers only
editor.addEventListener('beforeinput', (e) => {
if (e.inputType === 'insertText' || e.inputType === 'insertCompositionText') {
const newText = e.data || '';
const currentText = editor.textContent;
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const selectedText = range.toString();
const newContent = currentText.slice(0, range.startOffset) +
newText +
currentText.slice(range.endOffset);
if (!pattern.test(newContent)) {
e.preventDefault();
}
}
}
});readonly
The readonly attribute is ignored in Firefox. In other browsers, behavior is inconsistent. Implement readonly manually.
// Implement readonly manually
const editor = document.querySelector('[contenteditable]');
let isReadonly = false;
editor.addEventListener('beforeinput', (e) => {
if (isReadonly) {
e.preventDefault();
}
});
// Also prevent paste, drag-drop, etc.
editor.addEventListener('paste', (e) => {
if (isReadonly) {
e.preventDefault();
}
});
editor.addEventListener('drop', (e) => {
if (isReadonly) {
e.preventDefault();
}
});disabled
The disabled attribute is ignored in Safari. Implement disabled state manually.
// Manual disabled implementation
const editor = document.querySelector('[contenteditable]');
let isDisabled = false;
function setDisabled(disabled) {
isDisabled = disabled;
editor.setAttribute('contenteditable', disabled ? 'false' : 'true');
editor.setAttribute('aria-disabled', disabled ? 'true' : 'false');
editor.style.opacity = disabled ? '0.6' : '1';
editor.style.pointerEvents = disabled ? 'none' : 'auto';
}
editor.addEventListener('beforeinput', (e) => {
if (isDisabled) {
e.preventDefault();
}
});autocomplete
The autocomplete attribute does not trigger browser autocomplete suggestions. Browser autocomplete features are not available for contenteditable.
ℹ️ No Browser Autocomplete
Browser autocomplete suggestions (for forms, addresses, etc.) do not appear when typing in contenteditable regions, even when appropriate autocomplete attributes are set. You must implement custom autocomplete if needed.
Partially Supported Attributes
These attributes work in some browsers or scenarios, but have limitations or inconsistencies.
placeholder
The placeholder attribute does not work on contenteditable. You must implement it using CSS and JavaScript.
// CSS-based placeholder implementation
[contenteditable]:empty::before {
content: attr(data-placeholder);
color: #999;
pointer-events: none;
}
// JavaScript to handle focus behavior
const editor = document.querySelector('[contenteditable]');
editor.addEventListener('focus', () => {
if (editor.textContent.trim() === '') {
// Placeholder should remain visible
// But Safari may hide it - need workaround
}
});
editor.addEventListener('blur', () => {
if (editor.textContent.trim() === '') {
// Show placeholder again
}
});⚠️ Placeholder Disappears on Focus
In Safari, when using CSS ::before or ::after for placeholder, the placeholder disappears immediately when the element receives focus, even if the content is empty. This differs from standard input elements where placeholder persists until text is entered.
inputmode
The inputmode attribute should control the type of virtual keyboard on mobile devices, but it is ignored on contenteditable in iOS Safari.
<div contenteditable inputmode="numeric">
<!-- Should show numeric keyboard, but doesn't work on iOS -->
</div>
<div contenteditable inputmode="email">
<!-- Should show email keyboard, but doesn't work on iOS -->
</div>⚠️ inputmode Not Supported on iOS
In iOS Safari, the inputmode attribute is ignored on contenteditable. The default keyboard always appears. Numeric, email, or URL keyboards cannot be triggered.
autocapitalize
The autocapitalize attribute works inconsistently on contenteditable in iOS Safari. Behavior may differ from standard input elements.
<div contenteditable autocapitalize="sentences">
<!-- Should capitalize first letter of sentences -->
</div>
<div contenteditable autocapitalize="words">
<!-- Should capitalize first letter of words -->
</div>
<div contenteditable autocapitalize="none">
<!-- Should not capitalize -->
</div>⚠️ autocapitalize Inconsistency
In Safari on iOS, autocapitalize may not work as expected on contenteditable. Capitalization behavior may differ from standard inputs, and the attribute may be ignored in some cases.
autocorrect
The autocorrect attribute behaves differently on contenteditable compared to standard input elements in iOS Safari.
<div contenteditable autocorrect="on">
<!-- Should enable autocorrect -->
</div>
<div contenteditable autocorrect="off">
<!-- Should disable autocorrect -->
</div>⚠️ autocorrect May Not Respect Attribute
In Safari on iOS, autocorrect may not respect the attribute value on contenteditable. Autocorrect suggestions may appear even when autocorrect="off" is set, and behavior may differ from standard input elements.
enterkeyhint
The enterkeyhint attribute should control the label on the Enter key on mobile keyboards, but it is ignored on contenteditable in Android Chrome.
<div contenteditable enterkeyhint="send">
<!-- Should show "Send" on Enter key, but doesn't work on Android -->
</div>
<div contenteditable enterkeyhint="search">
<!-- Should show "Search" on Enter key, but doesn't work on Android -->
</div>⚠️ enterkeyhint Not Supported on Android
In Chrome on Android, the enterkeyhint attribute is ignored on contenteditable. The Enter key always shows the default label, and no customization is possible.
Platform-Specific Issues & Edge Cases
Browser-Specific Behavior
Safari
- placeholder: CSS-based placeholder disappears on focus even if content is empty
- lang: Does not affect spellcheck language - always uses browser default
- disabled: Attribute is ignored - element remains editable
- inputmode: Ignored on iOS - default keyboard always appears
- autocapitalize/autocorrect: Works inconsistently compared to standard inputs
Chrome/Edge
- autocomplete: Attribute is ignored - no browser autocomplete suggestions
- maxlength: Not supported - must implement manually
- enterkeyhint: Ignored on Android - default Enter key label always shown
Firefox
- readonly: Attribute is ignored - users can still edit
- dir: Dynamic changes during editing may not take effect immediately - caret position may be incorrect
OS & Keyboard-Specific Behavior
iOS
- inputmode: Completely ignored on contenteditable - cannot control keyboard type
- autocapitalize: Works inconsistently - may not respect attribute value
- autocorrect: May appear even when disabled - behavior differs from standard inputs
Android
- enterkeyhint: Ignored on contenteditable - default Enter key label always shown
- inputmode: May work in some browsers but behavior is inconsistent
Device-Specific Behavior
Mobile
- Virtual Keyboard Attributes: Most mobile-specific attributes (inputmode, enterkeyhint, autocapitalize, autocorrect) work inconsistently or not at all
- Touch Interaction: Some attributes may behave differently due to touch vs. mouse interaction
Tablet
- Hybrid Behavior: Tablets may show desktop or mobile behavior depending on browser and OS
- Keyboard Type: Physical keyboard vs. virtual keyboard affects attribute behavior
Best Practices
- Always Implement Validation Manually: Don't rely on HTML attributes for validation - use JavaScript with
beforeinputevents - Test Across Browsers: Attribute support varies significantly - test on Safari, Chrome, Firefox, and mobile browsers
- Provide Fallbacks: For unsupported attributes, provide JavaScript-based alternatives
- Use ARIA Attributes: For accessibility, use ARIA attributes (
aria-required,aria-invalid, etc.) alongside manual validation - Handle Mobile Separately: Mobile-specific attributes often don't work - implement custom solutions for mobile keyboards
- Disable Spellcheck During IME: Spellcheck can interfere with IME composition - disable it during composition
- Save/Restore Selection: When changing attributes dynamically (like
dir), save and restore selection to maintain caret position