Interactive and Dynamic Elements
1. Button Elements and Button Types
| Type | Behavior | Use Case | Default |
|---|---|---|---|
| submit | Submits parent form | Form submission | Yes (in forms) |
| button | No default action | Custom JavaScript actions | No |
| reset | Resets form to defaults | Clear form (use sparingly) | No |
Example: Button types and usage
<!-- Submit button (default in forms) -->
<form action="/submit">
<button type="submit">Submit</button>
</form>
<!-- Explicit button type (no form submission) -->
<button type="button" onclick="alert('Clicked!')">Click Me</button>
<!-- Reset button (clears form) -->
<form>
<input type="text" name="name">
<button type="reset">Clear Form</button>
</form>
<!-- Button with HTML content -->
<button type="button">
<svg width="16" height="16">
<circle cx="8" cy="8" r="7" fill="green"/>
</svg>
Save Draft
</button>
<!-- Disabled button -->
<button type="submit" disabled>Processing...</button>
<!-- Button with value (for multiple submits) -->
<form method="POST">
<button type="submit" name="action" value="save">Save</button>
<button type="submit" name="action" value="publish">Publish</button>
<button type="submit" name="action" value="delete" formnovalidate>Delete</button>
</form>
<!-- Button outside form (using form attribute) -->
<form id="myForm">
<input type="text" name="username">
</form>
<button type="submit" form="myForm">Submit from Outside</button>
<!-- Override form attributes -->
<form action="/default" method="POST">
<button type="submit">Normal Submit</button>
<button type="submit"
formaction="/alternative"
formmethod="GET">
Alternative Submit
</button>
</form>
<!-- Styled buttons with states -->
<style>
button {
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
button[type="submit"] {
background-color: #4caf50;
color: white;
}
button[type="submit"]:hover {
background-color: #45a049;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
opacity: 0.6;
}
button:focus {
outline: 2px solid #2196f3;
outline-offset: 2px;
}
</style>
Warning: Always specify
type attribute on buttons. Default is
type="submit" inside forms, which can cause unexpected submissions. Use type="button"
for JavaScript-only buttons.
2. Details and Summary Disclosure Widgets
| Element | Purpose | Required | Behavior |
|---|---|---|---|
| <details> | Disclosure widget container | Yes | Expandable/collapsible content |
| <summary> | Visible heading/toggle | Optional (defaults to "Details") | Clickable toggle control |
Example: Details and summary widgets
<!-- Basic details/summary -->
<details>
<summary>Click to expand</summary>
<p>This content is hidden until the user clicks the summary.</p>
</details>
<!-- Initially open -->
<details open>
<summary>Already Expanded</summary>
<p>This content is visible by default.</p>
</details>
<!-- FAQ accordion -->
<details>
<summary>What is HTML?</summary>
<p>HTML (HyperText Markup Language) is the standard markup language for creating web pages.</p>
</details>
<details>
<summary>What is CSS?</summary>
<p>CSS (Cascading Style Sheets) is used to style HTML elements.</p>
</details>
<details>
<summary>What is JavaScript?</summary>
<p>JavaScript is a programming language that adds interactivity to web pages.</p>
</details>
<!-- Exclusive accordion (only one open at a time) -->
<details name="accordion">
<summary>Section 1</summary>
<p>Content for section 1.</p>
</details>
<details name="accordion">
<summary>Section 2</summary>
<p>Content for section 2. Opening this closes Section 1.</p>
</details>
<details name="accordion">
<summary>Section 3</summary>
<p>Content for section 3.</p>
</details>
<!-- Nested details -->
<details>
<summary>Parent Level</summary>
<p>Parent content.</p>
<details>
<summary>Child Level</summary>
<p>Nested content.</p>
</details>
</details>
<!-- With complex content -->
<details>
<summary>View Product Details</summary>
<table>
<tr>
<th>Specification</th>
<th>Value</th>
</tr>
<tr>
<td>Weight</td>
<td>1.5 kg</td>
</tr>
<tr>
<td>Dimensions</td>
<td>30 x 20 x 10 cm</td>
</tr>
</table>
</details>
<!-- JavaScript event handling -->
<details id="myDetails">
<summary>Track Toggle Events</summary>
<p>Content here.</p>
</details>
<script>
const details = document.getElementById('myDetails');
details.addEventListener('toggle', (e) => {
if (details.open) {
console.log('Details expanded');
} else {
console.log('Details collapsed');
}
});
</script>
<!-- Styled details/summary -->
<style>
details {
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
margin: 10px 0;
}
summary {
font-weight: bold;
cursor: pointer;
padding: 5px;
user-select: none;
}
summary:hover {
background-color: #f0f0f0;
}
details[open] summary {
margin-bottom: 10px;
border-bottom: 1px solid #ddd;
}
/* Custom marker */
summary::marker {
content: '▶ ';
}
details[open] summary::marker {
content: '▼ ';
}
</style>
Note: The
name attribute creates an exclusive accordion where only one details
element with the same name can be open at a time (modern browsers only). Use the toggle event to
detect state changes.
3. Dialog and Modal Elements
| Attribute/Method | Purpose | Type | Modal Behavior |
|---|---|---|---|
| open (attr) | Show dialog (non-modal) | Attribute | No backdrop, no focus trap |
| show() | Show dialog (non-modal) | Method | No backdrop, no focus trap |
| showModal() | Show dialog (modal) | Method | Backdrop, focus trap, Esc closes |
| close() | Close dialog | Method | Returns returnValue |
Example: Dialog and modal implementations
<!-- Basic modal dialog -->
<dialog id="myDialog">
<h2>Dialog Title</h2>
<p>This is a modal dialog.</p>
<button onclick="document.getElementById('myDialog').close()">Close</button>
</dialog>
<button onclick="document.getElementById('myDialog').showModal()">Open Modal</button>
<!-- Dialog with form -->
<dialog id="formDialog">
<form method="dialog">
<h2>Enter Your Name</h2>
<input type="text" name="username" required>
<div>
<button type="submit" value="cancel">Cancel</button>
<button type="submit" value="confirm">Confirm</button>
</div>
</form>
</dialog>
<button onclick="openFormDialog()">Open Form Dialog</button>
<script>
const formDialog = document.getElementById('formDialog');
function openFormDialog() {
formDialog.showModal();
}
formDialog.addEventListener('close', () => {
console.log('Dialog closed with:', formDialog.returnValue);
});
</script>
<!-- Non-modal dialog (show vs showModal) -->
<dialog id="nonModal">
<p>This is a non-modal dialog. You can still interact with the page.</p>
<button onclick="document.getElementById('nonModal').close()">Close</button>
</dialog>
<button onclick="document.getElementById('nonModal').show()">Open Non-Modal</button>
<!-- Confirmation dialog -->
<dialog id="confirmDialog">
<h2>Confirm Action</h2>
<p>Are you sure you want to delete this item?</p>
<form method="dialog">
<button value="no">Cancel</button>
<button value="yes">Delete</button>
</form>
</dialog>
<button onclick="showConfirmDialog()">Delete Item</button>
<script>
const confirmDialog = document.getElementById('confirmDialog');
function showConfirmDialog() {
confirmDialog.showModal();
}
confirmDialog.addEventListener('close', () => {
if (confirmDialog.returnValue === 'yes') {
console.log('Item deleted');
} else {
console.log('Action cancelled');
}
});
</script>
<!-- Dialog with backdrop click to close -->
<dialog id="backdropDialog">
<h2>Click backdrop to close</h2>
<p>Content here.</p>
<button onclick="document.getElementById('backdropDialog').close()">Close</button>
</dialog>
<script>
const backdropDialog = document.getElementById('backdropDialog');
backdropDialog.addEventListener('click', (e) => {
const rect = backdropDialog.getBoundingClientRect();
if (
e.clientX < rect.left ||
e.clientX > rect.right ||
e.clientY < rect.top ||
e.clientY > rect.bottom
) {
backdropDialog.close();
}
});
</script>
<!-- Prevent Esc key closing (for critical dialogs) -->
<dialog id="criticalDialog">
<h2>Critical Action Required</h2>
<p>You must make a choice.</p>
<button onclick="document.getElementById('criticalDialog').close('done')">I Understand</button>
</dialog>
<script>
const criticalDialog = document.getElementById('criticalDialog');
criticalDialog.addEventListener('cancel', (e) => {
e.preventDefault(); // Prevent Esc from closing
});
</script>
<!-- Styled dialog -->
<style>
dialog {
border: none;
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
max-width: 500px;
}
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(3px);
}
dialog h2 {
margin-top: 0;
}
dialog button {
margin: 5px;
padding: 10px 20px;
}
</style>
Warning: Use
showModal() for true modal behavior (backdrop + focus trap + Esc).
show() opens dialog without modal behavior. Forms with method="dialog" automatically
close the dialog on submit and set returnValue to the clicked button's value.
4. Progress and Meter Indicators
| Element | Purpose | Use Case | Value Type |
|---|---|---|---|
| <progress> | Show task progress | File upload, loading, completion % | Dynamic (0 to max) |
| <meter> | Show measurement in range | Disk usage, ratings, gauge | Static measurement |
Progress Attributes:
| Attribute | Purpose |
|---|---|
| value | Current progress (0 to max) |
| max | Maximum value (default: 1) |
Note: Without value, shows indeterminate state.
Example: Progress and meter elements
<!-- Progress bar (determinate) -->
<label for="fileProgress">File Upload:</label>
<progress id="fileProgress" value="70" max="100">70%</progress>
<span>70%</span>
<!-- Progress bar (indeterminate) -->
<label>Loading...</label>
<progress></progress>
<!-- Updating progress with JavaScript -->
<progress id="dynamicProgress" value="0" max="100"></progress>
<button onclick="updateProgress()">Start Progress</button>
<script>
function updateProgress() {
const progress = document.getElementById('dynamicProgress');
let value = 0;
const interval = setInterval(() => {
value += 10;
progress.value = value;
if (value >= 100) {
clearInterval(interval);
alert('Complete!');
}
}, 500);
}
</script>
<!-- Meter: Disk usage (low is bad) -->
<label>Disk Usage:</label>
<meter value="70" min="0" max="100"
low="30" high="80" optimum="20">
70%
</meter>
<span>70 GB of 100 GB used</span>
<!-- Meter: Battery level (high is good) -->
<label>Battery:</label>
<meter value="85" min="0" max="100"
low="20" high="80" optimum="100">
85%
</meter>
<!-- Meter: Temperature (middle is optimal) -->
<label>Temperature:</label>
<meter value="72" min="32" max="212"
low="60" high="80" optimum="70">
72°F
</meter>
<!-- Rating meter -->
<label>Product Rating:</label>
<meter value="4.5" min="0" max="5"
low="2" high="4" optimum="5">
4.5 out of 5
</meter>
<!-- Multiple progress bars -->
<div>
<label>HTML Skills:</label>
<progress value="90" max="100"></progress> 90%
</div>
<div>
<label>CSS Skills:</label>
<progress value="85" max="100"></progress> 85%
</div>
<div>
<label>JavaScript Skills:</label>
<progress value="75" max="100"></progress> 75%
</div>
<!-- Styled progress and meter -->
<style>
progress, meter {
width: 200px;
height: 20px;
border-radius: 10px;
}
/* Progress bar styling */
progress {
appearance: none;
}
progress::-webkit-progress-bar {
background-color: #f0f0f0;
border-radius: 10px;
}
progress::-webkit-progress-value {
background-color: #4caf50;
border-radius: 10px;
transition: width 0.3s;
}
progress::-moz-progress-bar {
background-color: #4caf50;
border-radius: 10px;
}
/* Meter styling */
meter {
appearance: none;
}
meter::-webkit-meter-bar {
background-color: #f0f0f0;
border-radius: 10px;
}
/* Green when optimal */
meter::-webkit-meter-optimum-value {
background-color: #4caf50;
}
/* Yellow when suboptimal */
meter::-webkit-meter-suboptimum-value {
background-color: #ffc107;
}
/* Red when sub-suboptimal */
meter::-webkit-meter-even-less-good-value {
background-color: #f44336;
}
</style>
Note: Use
<progress> for tasks that change over time (uploads, downloads).
Use <meter> for static measurements (disk usage, ratings). Meter automatically colors based
on low, high, and optimum values.
5. Content Editable and Rich Text Editing
| Attribute | Values | Behavior | Use Case |
|---|---|---|---|
| contenteditable | true, false, plaintext-only | Makes element editable | Inline editing, WYSIWYG editors |
| spellcheck | true, false | Enable/disable spell check | Control spell checking |
Example: Content editable implementations
<!-- Basic contenteditable -->
<div contenteditable="true" style="border: 1px solid #ccc; padding: 10px;">
Click here to edit this text.
</div>
<!-- Plaintext only (no formatting) -->
<div contenteditable="plaintext-only" style="border: 1px solid #ccc; padding: 10px;">
This can only be edited as plain text.
</div>
<!-- Editable with spell check disabled -->
<div contenteditable="true" spellcheck="false">
Code example without spell checking.
</div>
<!-- Simple WYSIWYG editor -->
<div id="toolbar">
<button onclick="document.execCommand('bold')"><b>B</b></button>
<button onclick="document.execCommand('italic')"><i>I</i></button>
<button onclick="document.execCommand('underline')"><u>U</u></button>
<button onclick="document.execCommand('insertOrderedList')">OL</button>
<button onclick="document.execCommand('insertUnorderedList')">UL</button>
<button onclick="insertLink()">Link</button>
</div>
<div id="editor" contenteditable="true"
style="border: 1px solid #ccc; min-height: 200px; padding: 10px;">
Start typing here...
</div>
<script>
function insertLink() {
const url = prompt('Enter URL:');
if (url) {
document.execCommand('createLink', false, url);
}
}
</script>
<!-- Save and restore content -->
<div id="editableDiv" contenteditable="true"
style="border: 1px solid #ccc; padding: 10px;">
Edit this content and save it.
</div>
<button onclick="saveContent()">Save</button>
<button onclick="loadContent()">Load</button>
<button onclick="clearContent()">Clear</button>
<script>
function saveContent() {
const content = document.getElementById('editableDiv').innerHTML;
localStorage.setItem('savedContent', content);
alert('Content saved!');
}
function loadContent() {
const content = localStorage.getItem('savedContent');
if (content) {
document.getElementById('editableDiv').innerHTML = content;
} else {
alert('No saved content found.');
}
}
function clearContent() {
document.getElementById('editableDiv').innerHTML = '';
}
</script>
<!-- Track changes -->
<div id="trackedEditor" contenteditable="true"
style="border: 1px solid #ccc; padding: 10px; min-height: 100px;">
Edit me...
</div>
<p>Character count: <span id="charCount">0</span></p>
<script>
const trackedEditor = document.getElementById('trackedEditor');
const charCount = document.getElementById('charCount');
trackedEditor.addEventListener('input', () => {
const text = trackedEditor.textContent;
charCount.textContent = text.length;
});
</script>
<!-- Modern Selection API usage -->
<div id="modernEditor" contenteditable="true"
style="border: 1px solid #ccc; padding: 10px; min-height: 100px;">
Select text and use buttons below.
</div>
<button onclick="makeSelectionBold()">Make Bold</button>
<button onclick="wrapInSpan()">Wrap in Span</button>
<script>
function makeSelectionBold() {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const bold = document.createElement('strong');
range.surroundContents(bold);
}
}
function wrapInSpan() {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const span = document.createElement('span');
span.style.backgroundColor = 'yellow';
range.surroundContents(span);
}
}
</script>
<!-- Prevent certain keys -->
<div contenteditable="true"
onkeydown="return event.key !== 'Enter'"
style="border: 1px solid #ccc; padding: 10px;">
Single line only (Enter disabled)
</div>
Warning:
document.execCommand() is deprecated but
still widely used. For new projects, use the Selection API and DOM manipulation. Always sanitize user input from
contenteditable elements to prevent XSS attacks.
6. Drag and Drop API Integration
| Attribute/Event | Purpose | Applied To | Required |
|---|---|---|---|
| draggable | Make element draggable | Any element | Yes (for non-default draggable) |
| dragstart | Drag begins | Draggable element | Set data |
| drag | While dragging | Draggable element | Optional |
| dragend | Drag finished | Draggable element | Cleanup |
| dragenter | Drag enters drop zone | Drop target | Visual feedback |
| dragover | Drag over drop zone | Drop target | preventDefault() required |
| dragleave | Drag leaves drop zone | Drop target | Remove feedback |
| drop | Item dropped | Drop target | Handle drop |
Example: Drag and drop implementations
<!-- Basic drag and drop -->
<div id="draggable" draggable="true"
style="width: 100px; height: 100px; background: #4caf50; color: white;
display: flex; align-items: center; justify-content: center; cursor: move;">
Drag Me
</div>
<div id="dropzone"
style="width: 300px; height: 200px; border: 2px dashed #ccc;
margin-top: 20px; padding: 20px;">
Drop Here
</div>
<script>
const draggable = document.getElementById('draggable');
const dropzone = document.getElementById('dropzone');
// Drag events
draggable.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', 'Dragged element');
e.dataTransfer.effectAllowed = 'move';
draggable.style.opacity = '0.5';
});
draggable.addEventListener('dragend', (e) => {
draggable.style.opacity = '1';
});
// Drop zone events
dropzone.addEventListener('dragover', (e) => {
e.preventDefault(); // Required to allow drop
e.dataTransfer.dropEffect = 'move';
dropzone.style.backgroundColor = '#f0f0f0';
});
dropzone.addEventListener('dragleave', (e) => {
dropzone.style.backgroundColor = '';
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
const data = e.dataTransfer.getData('text/plain');
dropzone.appendChild(draggable);
dropzone.style.backgroundColor = '';
});
</script>
<!-- Sortable list -->
<ul id="sortableList" style="list-style: none; padding: 0;">
<li draggable="true" style="padding: 10px; margin: 5px; background: #f0f0f0; cursor: move;">Item 1</li>
<li draggable="true" style="padding: 10px; margin: 5px; background: #f0f0f0; cursor: move;">Item 2</li>
<li draggable="true" style="padding: 10px; margin: 5px; background: #f0f0f0; cursor: move;">Item 3</li>
<li draggable="true" style="padding: 10px; margin: 5px; background: #f0f0f0; cursor: move;">Item 4</li>
</ul>
<script>
const list = document.getElementById('sortableList');
let draggedItem = null;
list.addEventListener('dragstart', (e) => {
draggedItem = e.target;
e.dataTransfer.effectAllowed = 'move';
e.target.style.opacity = '0.5';
});
list.addEventListener('dragend', (e) => {
e.target.style.opacity = '1';
});
list.addEventListener('dragover', (e) => {
e.preventDefault();
const afterElement = getDragAfterElement(list, e.clientY);
if (afterElement == null) {
list.appendChild(draggedItem);
} else {
list.insertBefore(draggedItem, afterElement);
}
});
function getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('li:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
</script>
<!-- File drop zone -->
<div id="fileDropzone"
style="width: 400px; height: 200px; border: 2px dashed #ccc;
display: flex; align-items: center; justify-content: center;
font-size: 18px; color: #999;">
Drop files here
</div>
<div id="fileList"></div>
<script>
const fileDropzone = document.getElementById('fileDropzone');
const fileList = document.getElementById('fileList');
fileDropzone.addEventListener('dragover', (e) => {
e.preventDefault();
fileDropzone.style.borderColor = '#4caf50';
fileDropzone.style.backgroundColor = '#f0f9f0';
});
fileDropzone.addEventListener('dragleave', (e) => {
fileDropzone.style.borderColor = '#ccc';
fileDropzone.style.backgroundColor = '';
});
fileDropzone.addEventListener('drop', (e) => {
e.preventDefault();
fileDropzone.style.borderColor = '#ccc';
fileDropzone.style.backgroundColor = '';
const files = e.dataTransfer.files;
fileList.innerHTML = '<h3>Dropped Files:</h3>';
for (let i = 0; i < files.length; i++) {
const file = files[i];
fileList.innerHTML += `<p>${file.name} (${file.size} bytes)</p>`;
}
});
</script>
<!-- Custom drag image -->
<div id="customDrag" draggable="true"
style="width: 100px; height: 100px; background: #2196f3; color: white;
display: flex; align-items: center; justify-content: center; cursor: move;">
Custom Preview
</div>
<script>
const customDrag = document.getElementById('customDrag');
customDrag.addEventListener('dragstart', (e) => {
const dragImage = document.createElement('div');
dragImage.style.width = '150px';
dragImage.style.height = '50px';
dragImage.style.backgroundColor = '#ff5722';
dragImage.style.color = 'white';
dragImage.style.display = 'flex';
dragImage.style.alignItems = 'center';
dragImage.style.justifyContent = 'center';
dragImage.textContent = 'Custom Drag Image';
document.body.appendChild(dragImage);
e.dataTransfer.setDragImage(dragImage, 75, 25);
setTimeout(() => {
document.body.removeChild(dragImage);
}, 0);
});
</script>
Note: Must call
preventDefault() in dragover event to allow drop. Use
dataTransfer.files to access dropped files. Images, links, and text are draggable by default; other
elements need draggable="true".
Section 10 Key Takeaways
- Always specify
typeattribute on buttons (submit, button, reset) to avoid unexpected behavior - Use
<button>over<input type="button">for more flexible content (HTML, icons) - Details/summary provides native accordion without JavaScript; use
nameattribute for exclusive groups - Use
showModal()for true modal dialogs (backdrop + focus trap);show()for non-modal - Forms with
method="dialog"automatically close dialog and set returnValue - Use
<progress>for dynamic tasks,<meter>for static measurements - document.execCommand() is deprecated; use Selection API for modern rich text editing
- Always sanitize contenteditable content to prevent XSS attacks
- Must call preventDefault() in dragover event to enable dropping