logo hsb.horse
← Back to snippets index

Snippets

Clone a Native UI Element to Inherit Styles

A lightweight pattern that maintains visual consistency by cloning existing host app buttons with cloneNode, then replacing only the icon and text. No need to fight obfuscated CSS.

Published: Updated:

In browser extensions and embedded UIs for enterprise portals, replicating the host app’s design system is challenging. Rather than chasing obfuscated class names or framework-specific styles, cloning existing native elements and replacing only what’s necessary gives you visual consistency “for free.”

Code

interface CloneButtonOptions {
/** Selector for the template element to clone */
templateSelector: string
/** New text to replace */
text: string
/** New icon to replace (optional) */
icon?: string | HTMLElement
/** Click handler */
onClick: (event: MouseEvent) => void
}
/**
* Clone an existing button from the host app and generate a new button
*/
export const cloneNativeButton = (
options: CloneButtonOptions
): HTMLElement | null => {
const { templateSelector, text, icon, onClick } = options
// Get the template element
const template = document.querySelector<HTMLElement>(templateSelector)
if (!template) {
console.warn(`Template not found: ${templateSelector}`)
return null
}
// Create a deep clone (excluding event handlers)
const cloned = template.cloneNode(true) as HTMLElement
// Replace text node
const textNode = findTextNode(cloned)
if (textNode) {
textNode.textContent = text
}
// Replace icon (if specified)
if (icon) {
const iconElement = findIconElement(cloned)
if (iconElement) {
const newIcon = typeof icon === 'string'
? createIconFromString(icon)
: icon
iconElement.replaceWith(newIcon)
}
}
// Event listeners are not cloned, so attach new ones
cloned.addEventListener('click', onClick)
// Remove ID if present (avoid duplicates)
if (cloned.id) {
cloned.removeAttribute('id')
}
return cloned
}
/**
* Find the first text node within the element
*/
const findTextNode = (element: HTMLElement): Text | null => {
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
{
acceptNode: (node) => {
// Exclude whitespace-only nodes
return node.textContent?.trim()
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT
}
}
)
return walker.nextNode() as Text | null
}
/**
* Find the first icon element (svg, img, i, etc.)
*/
const findIconElement = (element: HTMLElement): Element | null => {
return (
element.querySelector('svg') ||
element.querySelector('img') ||
element.querySelector('i[class*="icon"]') ||
element.querySelector('[role="img"]')
)
}
/**
* Create an icon element from an HTML string
*/
const createIconFromString = (html: string): HTMLElement => {
const template = document.createElement('template')
template.innerHTML = html.trim()
return template.content.firstElementChild as HTMLElement
}

Usage

// Reuse GitHub's existing button style
const myButton = cloneNativeButton({
templateSelector: '.Button--primary',
text: 'Deploy',
icon: '<svg>...</svg>',
onClick: () => {
console.log('Deploying...')
}
})
// Append to DOM
document.querySelector('.toolbar')?.appendChild(myButton)

How It Works

  1. Use querySelector to retrieve the template element to clone
  2. Create a deep copy with cloneNode(true) (includes child elements)
  3. Traverse text nodes with TreeWalker and replace them
  4. Find icon elements with querySelector and replace them
  5. Event listeners are not cloned, so attach new ones with addEventListener
  6. Remove the id attribute if present to avoid duplicates

Note that cloneNode preserves styles, classes, and data attributes, but event listeners are not copied.

Benefits

  • Visual Consistency: Naturally inherits the host app’s design system
  • No CSS Required: No need to chase obfuscated class names or framework styles
  • Maintainable: Automatically follows host design updates
  • Lightweight: Implemented with DOM APIs only, no dependencies

Caveats

cloneNode copies the current state of the DOM, so dynamically changing classes or styles (like hover states) are not cloned. Also, if the template element is removed, this approach stops working, so include existence checks.

Applications

  • Add buttons with visual consistency in browser extensions
  • Embed features that naturally blend into complex enterprise portal design systems
  • Insert custom operation panels into third-party dashboards