Update v4.2 - New components, 10+ framework guides, and quality improvements. Visit Changelog

React + Vite

Using Preline UI with React + Vite

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.

Start with the right mental model

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.

Let autoInit run after React renders

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.

App.tsx
                        
                          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.

Choose the import style by how much control you need

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.

main.tsx
                        
                          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.

Dropdown.tsx
                        
                          import { HSDropdown } from "preline/non-auto";

                          HSDropdown.autoInit();
                        
                      

Single plugin packages are useful for smaller React surfaces

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.

Terminal
                        
                          npm install @preline/dropdown
                        
                      
Dropdown.tsx
                        
                          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.

Use manual instances when a component owns the DOM node

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.

Dropdown.tsx
                        
                          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>
                            );
                          }
                        
                      

Cleanup matters because Preline UI keeps registries

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.

Cleanup
                        
                          HSStaticMethods.cleanCollection("dropdown");
                          HSStaticMethods.cleanCollection(["dropdown", "overlay"]);
                        
                      

Optional dependencies only matter for the plugins that use them

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.

The tradeoff versus React-based libraries

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.

The practical checklist

  • Use preline/non-auto in React + Vite when you need explicit lifecycle control.
  • Run HSStaticMethods.autoInit() after React renders route content that contains Preline UI markup.
  • Create manual plugin instances with a ref when a React component owns one specific plugin node.
  • Call destroy() for manual instances and clean plugin collections only when you intentionally remove a whole group of initialized markup.
  • Install optional third-party dependencies only for the plugins that need them, such as datatables.net for Datatable or noUiSlider for Range Slider.

© 2026 Preline Labs.