@preline/copy-markup
v4.2.0
Published
Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.
Readme
Copy Markup
Copy input, select or other markups.
Contents
Overview
The Copy Markup component allows you to dynamically duplicate HTML elements (inputs, selects, or other markup) with a single click. It's useful for creating dynamic forms where users can add multiple instances of the same field.
Key Features:
- One-click element duplication
- Configurable copy limits
- Automatic element management
- Programmatic control via JavaScript API
- Event system for copy/delete tracking
- Support for any HTML markup
Installation
To get started, install Copy Markup plugin via npm, else you can skip this step if you are already using Preline UI as a package.
npm i @preline/copy-markupCSS
@import the plugin's CSS file into your Tailwind CSS file.
@import "tailwindcss";
/* @preline/copy-markup */
@import "./node_modules/@preline/copy-markup/theme.css";JavaScript
Include the JavaScript that powers the interactive elements near the end of your </body> tag:
<script src="./node_modules/@preline/copy-markup/index.js"></script>Manual Initialization
Use the non-auto entry if you need manual initialization. In this mode, automatic initialization on page load is not included, so the component should be initialized explicitly.
<script type="module">
import HSCopyMarkup from "@preline/copy-markup/non-auto.mjs";
new HSCopyMarkup(document.querySelector("#copy-markup"));
</script>Via Bundler
When using a bundler (Vite, webpack, etc.), import the plugin directly as an ES module.
@preline/copy-markup is the auto-init entry: it scans the DOM and initializes matching elements automatically.
import "@preline/copy-markup";@preline/copy-markup/non-auto is the manual entry: use it when you want explicit control over when initialization happens, either via autoInit() or by creating a specific instance yourself.
import HSCopyMarkup from "@preline/copy-markup/non-auto";
HSCopyMarkup.autoInit();
// Or initialize a specific element manually
const el = document.querySelector("#copy-markup");
if (el) new HSCopyMarkup(el);TypeScript
This package ships with TypeScript type definitions. No additional @types/ package is needed.
Basic usage
The following example demonstrates the minimal HTML structure required for a copy markup component. This is a base template without custom styling - you can apply your own CSS classes and styles as needed. Clicking the button will duplicate the target element.
<div id="hs-wrapper-for-copy-first">
<input id="hs-content-for-copy-first" type="text" class="py-3 px-4 block w-full border-gray-200 rounded-lg text-sm text-gray-800 focus:z-10 dark:bg-neutral-800 dark:border-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-500" placeholder="Enter Name">
</div>
<button type="button" data-hs-copy-markup='{
"targetSelector": "#hs-content-for-copy-first",
"wrapperSelector": "#hs-wrapper-for-copy-first",
"limit": 3
}' id="hs-copy-content-first">
Add Name
</button>Structure Requirements:
data-hs-copy-markup: Required on the trigger button, contains configuration options as JSONtargetSelector: Required, must be a valid CSS selector pointing to the element to copywrapperSelector: Required, must be a valid CSS selector pointing to the container where copies will be added- Target element must exist in the DOM
Initial State:
- One instance of the target element exists
- Copy button is enabled (unless limit is reached)
Configuration Options
Data Options
Data options are specified in the data-hs-copy-markup attribute as a JSON object.
| Option | Target Element | Type | Default | Description |
| --- | --- | --- | --- | --- |
| data-hs-copy-markup | Trigger button | - | - | Activate a Copy Markup by specifying on an element. Should be added to the button (trigger). |
| :targetSelector | Inside data-hs-copy-markup | string (CSS selector) | - | Specifies the target element to copy. The value must be a valid CSS selector. Required. |
| :wrapperSelector | Inside data-hs-copy-markup | string (CSS selector) | - | Specifies which element should be used for copying. The value must be a valid CSS selector. Required. |
| :limit | Inside data-hs-copy-markup | number | null | Specifies how many items you can copy until the button is disabled. null means no limit. |
Example:
<button data-hs-copy-markup='{
"targetSelector": "#hs-input-field",
"wrapperSelector": "#hs-wrapper",
"limit": 5
}'>
Add Field
</button>JavaScript API
The HSCopyMarkup object is available in the global window object after the plugin is loaded.
Instance Methods
These methods are called on a copy markup instance.
| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| delete(target) | target: HTMLElement | void | Remove the element associated to the target. The target should be a Node (HTMLElement). Use this to programmatically remove copied elements. |
| destroy() | None | void | Destroys the copy markup instance, removes all generated markup, classes, and event listeners. Use when removing copy markup from DOM. |
Static Methods
These methods are called directly on the HSCopyMarkup class.
| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| HSCopyMarkup.getInstance(target, isInstance) | target: HTMLElement \| string (CSS selector)isInstance: boolean (optional) | HSCopyMarkup \| { id: string \| number, element: HSCopyMarkup } \| null | Returns the copy markup instance associated with the target. If isInstance is true, returns collection item object { id, element } where element is the HSCopyMarkup instance. If isInstance is false or omitted, returns the HSCopyMarkup instance directly. Returns null if copy markup instance is not found. |
Usage Examples
Example 1: Deleting copied elements
// Get the copy markup instance
const instance = HSCopyMarkup.getInstance('#hs-copy-content', true);
if (instance) {
const { element } = instance;
const deleteBtn = document.querySelector('#hs-delete-btn');
deleteBtn.addEventListener('click', () => {
// Delete a specific copied element
const copiedElement = document.querySelector('#hs-copy-markup-item-first');
if (copiedElement) {
element.delete(copiedElement);
}
});
}Example 2: Using instance methods (recommended pattern)
// Get the copy markup instance
const instance = HSCopyMarkup.getInstance('#hs-copy-content', true);
if (instance) {
const { element } = instance;
// Access instance properties
console.log('Items:', element.items);
console.log('Limit:', element.limit);
// Delete a copied element programmatically
function removeCopiedItem(itemElement) {
element.delete(itemElement);
}
// Clean up when removing from DOM
function removeCopyMarkup() {
element.destroy();
}
}Example 3: Destroying copy markup instance
const instance = HSCopyMarkup.getInstance('#hs-copy-content', true);
if (instance) {
const { element } = instance;
const removeBtn = document.querySelector('#hs-remove-btn');
removeBtn.addEventListener('click', () => {
// Clean up before removing from DOM
element.destroy();
// Now safe to remove the element
document.querySelector('#hs-copy-content').remove();
});
}Events
Copy markup instances emit events that can be listened to for copy/delete tracking and custom behavior.
| Event Name | When Fired | Callback Parameter | Description |
| --- | --- | --- | --- |
| on:copy | When target element was copied | HTMLElement (copied element) | Fires when a new copy of the target element is created. Returns the newly created element. |
| on:delete | When target element was deleted | HTMLElement (deleted element) | Fires when a copied element is deleted. Returns the deleted element. |
Event Usage Example
// Get copy markup instance
const instance = HSCopyMarkup.getInstance('#hs-copy-content', true);
if (instance) {
const { element } = instance;
// Listen to copy event
element.on('copy', (copiedElement) => {
console.log('Element copied:', copiedElement);
// Perform actions after element is copied
// e.g., initialize other plugins on the new element, update counters
});
// Listen to delete event
element.on('delete', (deletedElement) => {
console.log('Element deleted:', deletedElement);
// Perform cleanup or state updates
});
}Common Patterns
Pattern 1: Limited Copies
Limit the number of copies that can be created.
<button data-hs-copy-markup='{
"targetSelector": "#hs-field",
"wrapperSelector": "#hs-wrapper",
"limit": 5
}'>
Add Field (Max 5)
</button>Pattern 2: Dynamic Form Fields
Create dynamic form fields with copy functionality.
<div id="hs-fields-wrapper">
<input id="hs-field-template" type="text" placeholder="Field name">
</div>
<button data-hs-copy-markup='{
"targetSelector": "#hs-field-template",
"wrapperSelector": "#hs-fields-wrapper"
}'>
Add Another Field
</button>License
Copyright (c) 2026 Preline Labs.
Licensed under the MIT License.
