Using Preline UI with Hugo
A practical guide to using Preline UI JavaScript plugins in Hugo projects, including static scripts, module imports, Hugo Pipes, cleanup, and optional dependencies.
View guideUpdate v4.2 - New components, 10+ framework guides, and quality improvements. Visit Changelog
A practical guide to wiring Preline UI into a Vite-powered HTML project using the current Preline UI module entries.
HTML + Vite is the closest setup to Preline UI's native contract. HTML owns the markup, Vite resolves package imports, and Preline UI attaches behavior to DOM nodes after they exist in the browser.
The current Preline UI package exposes two useful paths for this: direct non-auto plugin entries for static page markup and preline/non-auto for explicit scans, manual instances, and dynamic DOM updates.
Preline UI is a DOM-driven Tailwind CSS component system. It does not need a framework runtime to render components. Your HTML can contain dropdowns, overlays, tabs, selects, and other component markup directly, while Preline UI scans that markup and wires the JavaScript behavior.
Vite's job is dependency resolution and bundling. Use package imports in src/main.ts instead of linking into node_modules from HTML. That keeps the integration aligned with the package export map and lets Vite handle production output.
If the component markup is already in the page when the browser fires load, initialize only the plugin you need from its non-auto entry. Avoid relying on a bare side-effect import in a bundled Vite entry because bundlers can remove side-effect-only imports when the package metadata marks them as tree-shakeable.
import "./style.css";
import HSDropdown from "preline/plugins/dropdown-non-auto";
window.addEventListener("load", () => {
HSDropdown.autoInit();
});
<div class="hs-dropdown relative inline-flex">
<button type="button" class="hs-dropdown-toggle">
Actions
</button>
<div class="hs-dropdown-menu hidden">
<button type="button" role="menuitem">Edit</button>
</div>
</div>
This is a focused path for mostly static pages that only need a few plugins. The root preline module is a class/static-method export surface for ESM builds; in Vite, import the specific plugin non-auto entry when you want a smaller explicit initializer.
preline/non-auto exports the same static methods and plugin classes without registering page-load auto initialization. Use it when your Vite entry renders markup with JavaScript, replaces HTML sections, or needs repeatable scans.
import "./style.css";
import { HSStaticMethods } from "preline/non-auto";
document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
<div class="hs-dropdown relative inline-flex">
<button type="button" class="hs-dropdown-toggle">
Actions
</button>
<div class="hs-dropdown-menu hidden">
<button type="button" role="menuitem">Edit</button>
</div>
</div>
`;
HSStaticMethods.autoInit(["dropdown"]);
The collection keys come from the current static method map, for example dropdown, overlay, select, tooltip, tabs, datatable, and range-slider.
The full package is convenient when one Vite entry controls several Preline UI components. It gives you HSStaticMethods, named plugin classes, and the collection helpers from one declared surface.
import { HSDropdown, HSOverlay, HSStaticMethods } from "preline/non-auto";
HSStaticMethods.autoInit(["dropdown", "overlay"]);
HSDropdown.close("#actions-menu");
HSOverlay.open("#settings-modal");
The full package export map also contains plugin-specific paths under preline/plugins/*. For strict TypeScript examples, named imports from preline/non-auto are usually the smoother default because the declarations are already exposed from that entry.
Preline UI plugins can also be consumed from single-plugin dependencies when those packages are available in your dependency set, for example @preline/dropdown, @preline/overlay, @preline/select, or @preline/range-slider. This is useful when an HTML + Vite page only needs one interactive primitive.
npm install @preline/dropdown
import HSDropdown from "@preline/dropdown/non-auto";
HSDropdown.autoInit();
In that single-package setup, the auto entry is import "@preline/dropdown". It is useful for static HTML. The /non-auto entry is better when your Vite code decides exactly when the scan should happen.
autoInit is convenient for page-level scans. A manual instance is better when one script owns one specific DOM node and can destroy the plugin instance when that node is removed.
import { HSDropdown, type IHTMLElementFloatingUI } from "preline/non-auto";
const el = document.querySelector<HTMLElement>("#actions-menu");
const dropdown = el
? new HSDropdown(el as unknown as IHTMLElementFloatingUI)
: null;
export function removeActionsMenu() {
dropdown?.destroy();
el?.remove();
}
Preline UI stores plugin instances in internal collections such as window.$hsDropdownCollection. That registry lets plugins coordinate without a framework context and lets static methods find existing instances.
Most plugin autoInit methods filter collection entries whose elements are no longer in document and skip nodes that already have an instance. Still, when your Vite code intentionally removes a whole page section, cleaning the related collection keeps the registry explicit.
import { HSStaticMethods } from "preline/non-auto";
const app = document.querySelector<HTMLDivElement>("#app")!;
export function renderMenu() {
app.innerHTML = `
<div class="hs-dropdown relative inline-flex">
<button type="button" class="hs-dropdown-toggle">Actions</button>
<div class="hs-dropdown-menu hidden">Menu</div>
</div>
`;
HSStaticMethods.autoInit(["dropdown"]);
}
export function clearMenu() {
app.innerHTML = "";
HSStaticMethods.cleanCollection("dropdown");
}
Most core plugins do not use jQuery. Dropdowns, overlays, tooltips, popovers, tabs, and similar components use plain JavaScript. Positioning behavior uses @floating-ui/dom.
Datatable is different because it wraps DataTables.net, which depends on jQuery. The current static method map only wires Datatable when both DataTable and jQuery exist globally. Without those globals, HSStaticMethods.autoInit(["datatable"]) should not initialize a datatable instance.
Range Slider uses the JavaScript API from noUiSlider. Preline UI remains responsible for the Tailwind CSS markup and behavior wrapper; noUiSlider CSS classes do not need to be merged into Preline UI styling.
preline/plugins/dropdown-non-auto for mostly static HTML where markup exists before the page load event.preline/non-auto when Vite code renders or replaces markup and should decide when scans happen.preline/non-auto for strict TypeScript manual instances.@preline/dropdown/non-auto when a page only needs one plugin.destroy() for manual instances and cleanCollection() when removing initialized groups of markup.datatables.net for Datatable or noUiSlider for Range Slider.A practical guide to using Preline UI JavaScript plugins in Hugo projects, including static scripts, module imports, Hugo Pipes, cleanup, and optional dependencies.
View guideA practical guide to using Preline UI JavaScript plugins in React + Vite projects, including autoInit, module imports, cleanup, and optional dependencies.
View guide