Three utility functions for reading and writing HTML element attributes, with namespace support.
Getting attributes
Returns all attributes of an element as a plain object:
const getAttributes = (
element: Element,
namespace: string | null = null,
) => {
const attributeNames = element.getAttributeNames()
const attrs: Record<string, string | null> = {}
for (const attributeName of attributeNames) {
attrs[attributeName] = element.getAttributeNS(namespace, attributeName)
}
return attrs
}Usage:
const div = document.querySelector('div[data-id="42"]')!
getAttributes(div) // { 'data-id': '42', class: 'card', ... }Setting attributes
Sets attributes from an object. Passing null or undefined for a value removes that attribute:
const setAttributes = (
element: Element,
attributes: Record<string, string | number | boolean | null | undefined>,
namespace: string | null = null,
) => {
for (const [name, value] of Object.entries(attributes)) {
if (typeof value === 'undefined' || value === null) {
element.removeAttributeNS(namespace, name)
} else {
element.setAttributeNS(namespace, name, value.toString())
}
}
}This is handy when you need to update several attributes at once and want removal to be part of the same call:
setAttributes(button, {
'aria-expanded': true,
'aria-controls': 'menu-list',
'data-loading': null, // removes the attribute
})Stringifying attributes
Converts an attribute object into a string suitable for injecting into HTML. Skips undefined, null, NaN, and infinities:
function stringifyAttributes(attributes: Record<string, string | number | undefined | null | boolean>): string {
const result: string[] = []
for (const [key, value] of Object.entries(attributes)) {
if (
value !== undefined &&
value !== null &&
!Number.isNaN(value) &&
value !== Number.POSITIVE_INFINITY &&
value !== Number.NEGATIVE_INFINITY
) {
result.push(`${key}="${value.toString()}"`)
}
}
return result.join(' ')
}I use this in server-side rendering contexts where you’re building raw HTML strings and need attribute serialization without a DOM:
const attrs = stringifyAttributes({
id: 'main',
class: 'container',
hidden: undefined, // skipped
})
// 'id="main" class="container"'Namespaces
All three functions accept an optional namespace parameter. For regular HTML you can ignore it and pass null (the default). Namespaces matter for SVG and MathML attributes that have the same name as HTML attributes but different semantics.
For example, the href attribute on an SVG <use> element should be set with the XLink namespace in older browsers:
setAttributes(useElement, { href: '#icon-close' }, 'http://www.w3.org/1999/xlink')Modern browsers handle SVG href without a namespace, but if you’re targeting older environments or dealing with legacy SVG, the namespace parameter is there. For everything else, leave it as null.