Skip to content

Instantly share code, notes, and snippets.

@StoneyEagle
Created August 9, 2026 21:17
Show Gist options
  • Select an option

  • Save StoneyEagle/4287cf60b2b105586e556ccda1b4b45f to your computer and use it in GitHub Desktop.

Select an option

Save StoneyEagle/4287cf60b2b105586e556ccda1b4b45f to your computer and use it in GitHub Desktop.
Chainable DOM element builder utility (fluent create/addClasses/appendTo)
/**
* A chainable object returned after creating a new (or reusing an existing)
* DOM element, allowing further configuration or insertion into the document.
*
* @template T - The type of DOM element being built.
*/
export interface CreateElement<T extends Element> {
/**
* Returns the underlying DOM element.
*
* @returns The element being built.
*/
get: () => T;
/**
* Adds one or more CSS classes to the element.
*
* @param names - The class names to add. Falsy entries are skipped.
* @returns A chainable object for further configuration.
*/
addClasses: (names: string[]) => AddClasses<T>;
/**
* Appends the element as the last child of the given parent.
*
* @param parent - The parent element to append to.
* @returns A chainable object for further configuration.
*/
appendTo: (parent: Element) => AppendTo<T>;
/**
* Inserts the element as the first child of the given parent.
*
* @param parent - The parent element to prepend to.
* @returns A chainable object for further configuration.
*/
prependTo: (parent: Element) => AppendTo<T>;
/**
* Sets an HTML attribute on the element.
*
* @param name - The attribute name.
* @param value - The attribute value.
* @returns A chainable object for further configuration.
*/
setAttribute: (name: string, value: string) => AddClasses<T>;
/**
* Sets an inline CSS custom property or style property on the element,
* if it is an `HTMLElement`.
*
* @param name - The CSS property name.
* @param value - The CSS property value.
* @returns A chainable object for further configuration.
*/
setProperty: (name: string, value: string) => AddClasses<T>;
}
/**
* A chainable object returned after adding classes to an element, allowing
* further configuration or insertion into the document.
*
* @template T - The type of DOM element being built.
*/
export interface AddClasses<T extends Element> {
/**
* Returns the underlying DOM element.
*
* @returns The element being built.
*/
get: () => T;
/**
* Appends the element as the last child of the given parent.
*
* @param parent - The parent element to append to.
* @returns A chainable object for further configuration.
*/
appendTo: (parent: Element) => AppendTo<T>;
/**
* Inserts the element as the first child of the given parent.
*
* @param parent - The parent element to prepend to.
* @returns A chainable object for further configuration.
*/
prependTo: (parent: Element) => AppendTo<T>;
/**
* Adds one or more CSS classes to the element.
*
* @param names - The class names to add. Falsy entries are skipped.
* @returns A chainable object for further configuration.
*/
addClasses: (names: string[]) => AddClasses<T>;
/**
* Sets an HTML attribute on the element.
*
* @param name - The attribute name.
* @param value - The attribute value.
* @returns A chainable object for further configuration.
*/
setAttribute: (name: string, value: string) => AddClasses<T>;
/**
* Sets an inline CSS custom property or style property on the element,
* if it is an `HTMLElement`.
*
* @param name - The CSS property name.
* @param value - The CSS property value.
* @returns A chainable object for further configuration.
*/
setProperty: (name: string, value: string) => AddClasses<T>;
}
/**
* A chainable object returned after inserting an element into the document,
* allowing further class configuration.
*
* @template T - The type of DOM element being built.
*/
export interface AppendTo<T extends Element> {
/**
* Returns the underlying DOM element.
*
* @returns The element being built.
*/
get: () => T;
/**
* Adds one or more CSS classes to the element.
*
* @param names - The class names to add. Falsy entries are skipped.
* @returns A chainable object for further configuration.
*/
addClasses: (names: string[]) => AddClasses<T>;
}
/**
* Creates a new DOM element of the given tag type, or reuses an existing
* element with a matching id when `unique` is set, and returns a chainable
* builder for configuring and inserting it.
*
* @param type - The HTML tag name to create, e.g. `'div'` or `'span'`.
* @param id - The id to assign to the element (or to look up when `unique` is set).
* @param unique - When true, reuses an existing element with the given id instead of creating a new one.
* @returns A chainable builder for the created or reused element.
* @template K - The HTML tag name key, used to infer the concrete element type.
*/
export function createElement<K extends keyof HTMLElementTagNameMap>(
type: K,
id: string,
unique?: boolean,
): CreateElement<HTMLElementTagNameMap[K]> {
let el: HTMLElementTagNameMap[K];
if (unique) {
const existing = document.querySelector<HTMLElementTagNameMap[K]>(`#${CSS.escape(id)}`);
el = existing ?? document.createElement(type);
}
else {
el = document.createElement(type);
}
if (!el.id)
el.id = id;
return {
get: () => el,
addClasses: (names: string[]) => addClasses(el, names),
appendTo: (parent: Element) => {
parent.appendChild(el);
return makeAppendTo(el);
},
prependTo: (parent: Element) => {
parent.insertBefore(el, parent.firstChild);
return makeAppendTo(el);
},
setAttribute: (name: string, value: string) => {
el.setAttribute(name, value);
return makeAddClasses(el);
},
setProperty: (name: string, value: string) => {
if (el instanceof HTMLElement)
el.style.setProperty(name, value);
return makeAddClasses(el);
},
};
}
/**
* Adds one or more CSS classes to an element and returns a chainable builder
* for further configuration.
*
* @param el - The element to add classes to.
* @param names - The class names to add. Falsy entries are skipped.
* @returns A chainable builder for the element.
* @template T - The type of DOM element.
*/
export function addClasses<T extends Element>(el: T, names: string[]): AddClasses<T> {
for (const name of names) {
if (name)
el.classList.add(name);
}
return makeAddClasses(el);
}
/**
* Removes one or more CSS classes from an element.
*
* @param el - The element to remove classes from.
* @param names - The class names to remove. Falsy entries are skipped.
* @returns The same element, for further use.
* @template T - The type of DOM element.
*/
export function removeClasses<T extends Element>(el: T, names: string[]): T {
for (const name of names) {
if (name)
el.classList.remove(name);
}
return el;
}
const SVG_NS = 'http://www.w3.org/2000/svg';
/**
* Creates a new SVG root element with the given id and viewBox.
*
* @param id - The id to assign to the SVG element.
* @param viewBox - The value of the `viewBox` attribute, e.g. `'0 0 24 24'`.
* @returns The created `SVGSVGElement`.
*/
export function createSVG(id: string, viewBox: string): SVGSVGElement {
const svg = document.createElementNS(SVG_NS, 'svg');
svg.setAttribute('id', id);
svg.setAttribute('viewBox', viewBox);
svg.setAttribute('xmlns', SVG_NS);
return svg;
}
/**
* Creates a new accessible `<button>` element with a click handler.
*
* @param id - The id to assign to the button.
* @param label - The accessible label, used for both `aria-label` and `title`.
* @param onClick - The click event handler to attach to the button.
* @returns The created `HTMLButtonElement`.
*/
export function createButton(id: string, label: string, onClick: (event: Event) => void): HTMLButtonElement {
const button = document.createElement('button');
button.id = id;
button.type = 'button';
button.setAttribute('aria-label', label);
button.title = label;
button.addEventListener('click', onClick);
return button;
}
/**
* Builds a chainable object for an element that has just been inserted into
* the document, exposing only class configuration.
*
* @param el - The element that was inserted.
* @returns A chainable builder exposing `get` and `addClasses`.
* @template T - The type of DOM element.
*/
function makeAppendTo<T extends Element>(el: T): AppendTo<T> {
return {
get: () => el,
addClasses: (names: string[]) => addClasses(el, names),
};
}
/**
* Builds a chainable object for an element that has just had classes,
* attributes, or properties applied, exposing the full set of remaining
* configuration and insertion methods.
*
* @param el - The element being configured.
* @returns A chainable builder exposing insertion and further configuration methods.
* @template T - The type of DOM element.
*/
function makeAddClasses<T extends Element>(el: T): AddClasses<T> {
return {
get: () => el,
appendTo: (parent: Element) => {
parent.appendChild(el);
return makeAppendTo(el);
},
prependTo: (parent: Element) => {
parent.insertBefore(el, parent.firstChild);
return makeAppendTo(el);
},
addClasses: (names: string[]) => addClasses(el, names),
setAttribute: (name: string, value: string) => {
el.setAttribute(name, value);
return makeAddClasses(el);
},
setProperty: (name: string, value: string) => {
if (el instanceof HTMLElement)
el.style.setProperty(name, value);
return makeAddClasses(el);
},
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment