JavaScript library that extends HTML with AJAX, CSS Transitions, WebSockets, and Server-Sent Events through declarative attributes
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Functions for manipulating DOM elements including class management, content swapping, and element removal with animation support.
Removes elements from the DOM with optional delay for animations.
/**
* Removes an element from the DOM
* @param elt - Element to remove
* @param delay - Optional delay in milliseconds before removal
*/
function remove(elt: Node, delay?: number): void;Usage Examples:
// Immediate removal
const element = document.getElementById('temp-message');
htmx.remove(element);
// Delayed removal (useful for animations)
const notification = document.querySelector('.notification');
htmx.remove(notification, 3000); // Remove after 3 seconds
// Remove with CSS animation
const card = document.getElementById('card-123');
card.classList.add('fade-out'); // Add fade-out animation class
htmx.remove(card, 500); // Remove after animation completes
// Remove multiple elements
const errorMessages = document.querySelectorAll('.error-message');
errorMessages.forEach(msg => {
htmx.remove(msg, 2000);
});Add, remove, and toggle CSS classes with optional delays for animations.
/**
* Adds a class to the given element
* @param elt - Target element or CSS selector
* @param clazz - Class name to add
* @param delay - Optional delay in milliseconds
*/
function addClass(elt: Element | string, clazz: string, delay?: number): void;
/**
* Removes a class from the given element
* @param node - Target element or CSS selector
* @param clazz - Class name to remove
* @param delay - Optional delay in milliseconds
*/
function removeClass(node: Node | string, clazz: string, delay?: number): void;
/**
* Toggles the given class on an element
* @param elt - Target element or CSS selector
* @param clazz - Class name to toggle
*/
function toggleClass(elt: Element | string, clazz: string): void;Usage Examples:
// Add class immediately
htmx.addClass('#message', 'visible');
htmx.addClass(document.getElementById('panel'), 'expanded');
// Add class with delay
htmx.addClass('.loading-spinner', 'active', 1000);
// Remove class
htmx.removeClass('#dialog', 'hidden');
htmx.removeClass('.button', 'disabled', 500);
// Toggle class
htmx.toggleClass('#menu', 'open');
htmx.toggleClass('.theme-switcher', 'dark-mode');
// Animation sequences
const button = document.getElementById('save-button');
htmx.addClass(button, 'loading');
// ... perform save operation ...
htmx.removeClass(button, 'loading');
htmx.addClass(button, 'success', 100);
htmx.removeClass(button, 'success', 2000);Ensures only a specific element has a class among its siblings.
/**
* Takes the given class from siblings (ensures only this element has the class)
* @param elt - Target element or CSS selector
* @param clazz - Class name to take from siblings
*/
function takeClass(elt: Node | string, clazz: string): void;Usage Examples:
// Tab selection - only one tab can be active
const activeTab = document.getElementById('tab-2');
htmx.takeClass(activeTab, 'active'); // Removes 'active' from siblings, adds to this element
// Navigation menu
htmx.takeClass('#nav-home', 'current-page');
// Card selection in a grid
const selectedCard = document.querySelector('.card[data-id="123"]');
htmx.takeClass(selectedCard, 'selected');
// Radio button-like behavior for custom elements
document.addEventListener('click', function(event) {
if (event.target.matches('.custom-radio')) {
htmx.takeClass(event.target, 'checked');
}
});
// Accordion panels - only one can be expanded
function expandPanel(panelId) {
const panel = document.getElementById(panelId);
htmx.takeClass(panel, 'expanded');
}Performs complete content replacement with the full htmx swapping pipeline including transitions and focus management.
/**
* Implements complete swapping pipeline including transitions, focus preservation, etc.
* @param target - Target element or CSS selector
* @param content - HTML content to swap in
* @param swapSpec - Swap specification object
* @param swapOptions - Optional swap options
*/
function swap(target: string | Element, content: string, swapSpec: HtmxSwapSpecification, swapOptions?: SwapOptions): void;
interface HtmxSwapSpecification {
swapStyle: HtmxSwapStyle;
swapDelay: number;
settleDelay: number;
transition?: boolean;
ignoreTitle?: boolean;
head?: string;
scroll?: "top" | "bottom" | number;
scrollTarget?: string;
show?: string;
showTarget?: string;
focusScroll?: boolean;
}
interface SwapOptions {
select?: string;
selectOOB?: string;
eventInfo?: any;
anchor?: string;
contextElement?: Element;
afterSwapCallback?: swapCallback;
afterSettleCallback?: swapCallback;
beforeSwapCallback?: swapCallback;
title?: string;
historyRequest?: boolean;
}
type HtmxSwapStyle = "innerHTML" | "outerHTML" | "beforebegin" | "afterbegin" | "beforeend" | "afterend" | "delete" | "none" | string;
type swapCallback = () => any;Basic Swap Examples:
// Simple innerHTML replacement
htmx.swap('#content', '<h1>New Content</h1>', {
swapStyle: 'innerHTML',
swapDelay: 0,
settleDelay: 20
});
// Replace entire element
htmx.swap('#old-element', '<div id="new-element">Updated</div>', {
swapStyle: 'outerHTML',
swapDelay: 0,
settleDelay: 20
});
// Append content
htmx.swap('#list', '<li>New Item</li>', {
swapStyle: 'beforeend',
swapDelay: 0,
settleDelay: 20
});
// Prepend content
htmx.swap('#notifications', '<div class="alert">New notification</div>', {
swapStyle: 'afterbegin',
swapDelay: 100,
settleDelay: 50
});Advanced Swap Examples:
// Swap with transition effects
htmx.swap('#panel', '<div class="panel-content">Updated content</div>', {
swapStyle: 'innerHTML',
swapDelay: 200,
settleDelay: 300,
transition: true
}, {
beforeSwapCallback: () => {
console.log('About to swap content');
},
afterSwapCallback: () => {
console.log('Content swapped');
},
afterSettleCallback: () => {
console.log('Swap settled');
// Initialize new content
initializeNewComponents();
}
});
// Swap with scrolling
htmx.swap('#main-content', newPageContent, {
swapStyle: 'innerHTML',
swapDelay: 0,
settleDelay: 20,
scroll: 'top',
focusScroll: true
}, {
title: 'New Page Title'
});
// Conditional swapping based on content
function smartSwap(target, content) {
const swapSpec = {
swapStyle: 'innerHTML',
swapDelay: 0,
settleDelay: 20
};
const swapOptions = {
beforeSwapCallback: () => {
// Add loading state
htmx.addClass(target, 'loading');
},
afterSwapCallback: () => {
// Remove loading state
htmx.removeClass(target, 'loading');
},
afterSettleCallback: () => {
// Announce to screen readers
const announcement = document.createElement('div');
announcement.setAttribute('role', 'status');
announcement.setAttribute('aria-live', 'polite');
announcement.textContent = 'Content updated';
document.body.appendChild(announcement);
setTimeout(() => htmx.remove(announcement), 1000);
}
};
htmx.swap(target, content, swapSpec, swapOptions);
}Handle content that needs to be swapped into different parts of the page.
// Process out-of-band content from server response
function handleOOBContent(responseHTML) {
const parser = new DOMParser();
const doc = parser.parseFromString(responseHTML, 'text/html');
// Find elements with hx-swap-oob attribute
const oobElements = doc.querySelectorAll('[hx-swap-oob]');
oobElements.forEach(element => {
const oobSpec = element.getAttribute('hx-swap-oob');
const [swapStyle, target] = oobSpec.split(':');
const targetElement = target ? document.querySelector(target) :
document.getElementById(element.id);
if (targetElement) {
htmx.swap(targetElement, element.outerHTML, {
swapStyle: swapStyle || 'innerHTML',
swapDelay: 0,
settleDelay: 20
});
}
});
}
// Manual out-of-band swap
function updateSidebar(content) {
htmx.swap('#sidebar', content, {
swapStyle: 'innerHTML',
swapDelay: 0,
settleDelay: 20
}, {
selectOOB: '#sidebar-content',
afterSettleCallback: () => {
// Reinitialize sidebar components
initializeSidebar();
}
});
}Coordinating DOM manipulation with CSS animations and transitions.
// Fade in new content
function fadeInContent(target, content) {
htmx.swap(target, content, {
swapStyle: 'innerHTML',
swapDelay: 0,
settleDelay: 20
}, {
afterSwapCallback: () => {
htmx.addClass(target, 'fade-in');
},
afterSettleCallback: () => {
// Remove animation class after animation completes
setTimeout(() => {
htmx.removeClass(target, 'fade-in');
}, 300);
}
});
}
// Slide animation sequence
function slideReplaceContent(target, content) {
// Slide out current content
htmx.addClass(target, 'slide-out');
// Replace content after slide-out animation
setTimeout(() => {
htmx.swap(target, content, {
swapStyle: 'innerHTML',
swapDelay: 0,
settleDelay: 20
}, {
afterSwapCallback: () => {
htmx.removeClass(target, 'slide-out');
htmx.addClass(target, 'slide-in');
},
afterSettleCallback: () => {
setTimeout(() => {
htmx.removeClass(target, 'slide-in');
}, 300);
}
});
}, 300);
}// Web Animations API integration
function animatedSwap(target, content, animation = 'fadeIn') {
const element = typeof target === 'string' ? document.querySelector(target) : target;
const animations = {
fadeIn: [
{ opacity: 0 },
{ opacity: 1 }
],
slideUp: [
{ transform: 'translateY(20px)', opacity: 0 },
{ transform: 'translateY(0)', opacity: 1 }
],
scale: [
{ transform: 'scale(0.8)', opacity: 0 },
{ transform: 'scale(1)', opacity: 1 }
]
};
htmx.swap(element, content, {
swapStyle: 'innerHTML',
swapDelay: 0,
settleDelay: 20
}, {
afterSwapCallback: () => {
element.animate(animations[animation], {
duration: 300,
easing: 'ease-out',
fill: 'both'
});
}
});
}
// GSAP integration example
function gsapSwap(target, content) {
const element = typeof target === 'string' ? document.querySelector(target) : target;
// Animate out current content
gsap.to(element, {
opacity: 0,
y: -20,
duration: 0.2,
onComplete: () => {
// Swap content
htmx.swap(element, content, {
swapStyle: 'innerHTML',
swapDelay: 0,
settleDelay: 20
}, {
afterSwapCallback: () => {
// Animate in new content
gsap.fromTo(element,
{ opacity: 0, y: 20 },
{ opacity: 1, y: 0, duration: 0.3 }
);
}
});
}
});
}DOM manipulation with accessibility in mind.
// Announce content changes to screen readers
function accessibleSwap(target, content, announcement) {
htmx.swap(target, content, {
swapStyle: 'innerHTML',
swapDelay: 0,
settleDelay: 20
}, {
afterSettleCallback: () => {
// Create announcement for screen readers
const announcer = document.createElement('div');
announcer.setAttribute('aria-live', 'polite');
announcer.setAttribute('aria-atomic', 'true');
announcer.className = 'sr-only';
announcer.textContent = announcement || 'Content updated';
document.body.appendChild(announcer);
// Clean up announcement
setTimeout(() => htmx.remove(announcer), 1000);
// Manage focus if needed
const newFocusTarget = document.querySelector(target + ' [autofocus]');
if (newFocusTarget) {
newFocusTarget.focus();
}
}
});
}
// Preserve focus context during swaps
function focusAwareSwap(target, content) {
const activeElement = document.activeElement;
const focusPath = getFocusPath(activeElement);
htmx.swap(target, content, {
swapStyle: 'innerHTML',
swapDelay: 0,
settleDelay: 20
}, {
afterSettleCallback: () => {
// Attempt to restore focus
restoreFocus(focusPath) || focusFirstInteractive(target);
}
});
}
function getFocusPath(element) {
const path = [];
let current = element;
while (current && current !== document.body) {
if (current.id) {
path.unshift('#' + current.id);
break;
} else if (current.className) {
path.unshift('.' + current.className.split(' ')[0]);
}
current = current.parentElement;
}
return path.join(' ');
}
function restoreFocus(focusPath) {
if (!focusPath) return false;
const element = document.querySelector(focusPath);
if (element && element.focus) {
element.focus();
return true;
}
return false;
}
function focusFirstInteractive(container) {
const target = typeof container === 'string' ? document.querySelector(container) : container;
const focusable = target.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (focusable) {
focusable.focus();
}
}