Using Preline UI with HTML + Vite
A practical guide to using Preline UI JavaScript plugins in HTML + Vite projects, including direct plugin imports, non-auto timing, manual instances, 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 React without fighting the React lifecycle.
React and Preline UI fit together cleanly when the responsibilities are clear. React creates the DOM. Preline UI reads that DOM and wires up the interaction layer. The small amount of lifecycle work you add in React is what keeps that handoff predictable.
This guide walks through the integration choices that matter in a real Vite project: when to run autoInit, when to import only a single plugin, when to create an instance manually, and how to avoid stale plugin references as React mounts and unmounts UI.
The easiest way to use Preline UI in React is to stop thinking of it as a React component package. Preline UI is a DOM-driven Tailwind CSS component system: React renders the markup, and Preline UI attaches behavior to that markup after it exists in the document.
That distinction matters. A dropdown, overlay, select, tab, or tooltip can be written once as Tailwind CSS markup and still work in React, plain HTML, Astro, Nuxt, Laravel, Rails, and other stacks. React stays responsible for rendering and state. Preline UI takes care of the JavaScript behavior around the DOM nodes.
autoInit is the bridge between those two worlds. It scans the current DOM, finds Preline UI markup, and creates plugin instances for anything that has not already been initialized. In a React Router app, route changes are the important moment, because each route can introduce fresh markup.
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
import { HSStaticMethods } from "preline/non-auto";
function App() {
const location = useLocation();
useEffect(() => {
HSStaticMethods.autoInit();
}, [location.pathname]);
return (
...
);
}
Static browser scripts and single-package auto entries can initialize on page load. In React + Vite, preline/non-auto is usually a better default: nothing runs until your app decides the DOM is ready.
For a full Preline UI installation, start with preline/non-auto. You still get the plugin classes and static helpers, but you avoid plugin auto-entry page-load behavior that is less predictable inside a React app.
import { HSStaticMethods } from "preline/non-auto";
HSStaticMethods.autoInit(["dropdown", "overlay"]);
If the page only needs one plugin class from the full package, import that class from preline/non-auto. It keeps TypeScript on the package's declared surface while still letting your code decide when to initialize.
import { HSDropdown } from "preline/non-auto";
HSDropdown.autoInit();
Not every React app needs the whole library. 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. That keeps the integration focused when only a small part of the UI needs Preline behavior.
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". Use it for simple pages. In React components, the /non-auto entry is the safer habit because initialization stays aligned with mount and route timing.
autoInit is good for page-level markup. Manual instances are better when a React component owns one specific plugin root and can clean it up directly. This is especially useful for reusable components, conditionally mounted overlays, and UI that appears and disappears often.
import { useEffect, useRef } from "react";
import { HSDropdown, type IHTMLElementFloatingUI } from "preline/non-auto";
export default function Dropdown() {
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!ref.current) return;
const dropdown = new HSDropdown(ref.current as unknown as IHTMLElementFloatingUI);
return () => {
dropdown.destroy();
};
}, []);
return (
<div ref={ref} className="hs-dropdown relative inline-flex">
...
</div>
);
}
Preline UI stores plugin instances in internal collections such as window.$hsDropdownCollection. That is not accidental global state; it is how plugins coordinate across environments where there is no React context. A dropdown can find other dropdowns, an overlay can coordinate with related behavior, and shared static methods can work consistently outside React too.
In React, the practical consequence is simple: if you create an instance manually, destroy it on unmount. If you initialize a whole section by collection and later remove that section, clean the relevant collection intentionally.
HSStaticMethods.cleanCollection("dropdown");
HSStaticMethods.cleanCollection(["dropdown", "overlay"]);
Most core plugins do not use jQuery. Dropdowns, overlays, tooltips, popovers, and similar components use plain JavaScript. Where positioning is needed, Preline UI uses @floating-ui/dom.
jQuery only enters the picture for Datatable because datatables.net depends on it. If jQuery and DataTables are not present, Datatable should not initialize. That dependency does not affect dropdowns, overlays, tooltips, or the rest of the core plugin set.
Range Slider is similar in spirit: it uses the JavaScript API from noUiSlider, while Preline UI remains responsible for the Tailwind CSS markup and behavior wrapper. You do not need to bring in noUiSlider CSS just to make Preline UI styling work.
React-based libraries are a strong fit when a product is purely React and every primitive should live as a React component. Preline UI makes a different bet. It keeps the markup portable and the JavaScript behavior framework-agnostic, so the same component system can run in React, Vite, plain HTML, Astro, Nuxt, Laravel, Rails, and similar environments.
The cost is that React developers need to be deliberate about lifecycle. The benefit is portability, consistent Tailwind CSS markup, maintained JavaScript behavior, and accessibility logic that can be shared across stacks instead of being rebuilt for each one.
preline/non-auto in React + Vite when you need explicit lifecycle control.HSStaticMethods.autoInit() after React renders route content that contains Preline UI markup.ref when a React component owns one specific plugin node.destroy() for manual instances and clean plugin collections only when you intentionally remove a whole group of initialized markup.datatables.net for Datatable or noUiSlider for Range Slider.A practical guide to using Preline UI JavaScript plugins in HTML + Vite projects, including direct plugin imports, non-auto timing, manual instances, cleanup, and optional dependencies.
View guideA practical guide to using Preline UI JavaScript plugins in Next.js projects, including client components, App Router navigation, module imports, cleanup, and optional dependencies.
View guide