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

HTML + Vite

Using Preline UI with HTML + Vite

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.

Start with the HTML + Vite model

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.

Initialize static page markup explicitly

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.

src/main.ts
                        
                          import "./style.css";
                          import HSDropdown from "preline/plugins/dropdown-non-auto";

                          window.addEventListener("load", () => {
                            HSDropdown.autoInit();
                          });
                        
                      
index.html
                        
                          <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.

Use non-auto when your code controls timing

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.

src/main.ts
                        
                          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.

Choose imports by the amount of code you need

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.

src/main.ts
                        
                          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.

Single plugin packages keep small pages focused

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.

Terminal
                        
                          npm install @preline/dropdown
                        
                      
src/main.ts
                        
                          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.

Use manual instances for one owned node

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.

src/dropdown.ts
                        
                          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();
                          }
                        
                      

Rescan dynamic DOM and clean deliberately

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.

src/main.ts
                        
                          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");
                          }
                        
                      

Install optional dependencies only when a plugin needs them

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.

The practical checklist

  • Use direct non-auto plugin entries such as preline/plugins/dropdown-non-auto for mostly static HTML where markup exists before the page load event.
  • Use preline/non-auto when Vite code renders or replaces markup and should decide when scans happen.
  • Use named plugin classes from preline/non-auto for strict TypeScript manual instances.
  • When available in your dependency set, use single plugin packages such as @preline/dropdown/non-auto when a page only needs one plugin.
  • Call destroy() for manual instances and cleanCollection() when removing initialized groups of markup.
  • Install optional dependencies only for the plugins that need them, such as datatables.net for Datatable or noUiSlider for Range Slider.

© 2026 Preline Labs.