Using Preline UI with React + Vite
A practical guide to using Preline UI JavaScript plugins in React + Vite projects, including autoInit, module imports, 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 Angular without fighting Angular rendering and routing.
Angular and Preline UI work best when Angular owns rendering and Preline UI owns the DOM behavior after the view exists. The integration is small, but the timing matters: initialize after Angular has placed the markup in the document, and clean up when Angular removes it.
The current Preline UI package gives Angular apps a module-first path: preline/non-auto for explicit scans, named plugin classes for manual instances, and internal collections that can be cleaned when Angular removes routed or component-owned markup.
Preline UI is not an Angular component library. It is a DOM-driven Tailwind CSS component system. Angular renders the template, then Preline UI scans the resulting DOM and attaches behavior to elements such as dropdowns, overlays, tabs, tooltips, and selects.
That is the reason the same markup can move between Angular, React, Vue, plain HTML, Astro, Laravel, Rails, and similar stacks. It also means Angular lifecycle hooks are part of the integration surface. If the DOM is not there yet, Preline UI has nothing to initialize.
For markup owned by a component view, ngAfterViewInit is the straightforward place to initialize Preline UI. Use preline/non-auto so initialization stays explicit after Angular has finished rendering.
import { AfterViewInit, Component, OnDestroy } from "@angular/core";
import { HSStaticMethods } from "preline/non-auto";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
})
export class AppComponent implements AfterViewInit, OnDestroy {
ngAfterViewInit() {
HSStaticMethods.autoInit(["dropdown", "overlay"]);
}
ngOnDestroy() {
HSStaticMethods.cleanCollection(["dropdown", "overlay"]);
}
}
autoInit is collection-aware. Calling it again does not create duplicate dropdown instances for the same node; it initializes markup that has not been initialized yet.
Route changes are the common case where Angular replaces a large part of the page. Listen for NavigationEnd, defer one microtask, then run autoInit against the new route markup.
import { Component, OnDestroy, OnInit } from "@angular/core";
import { NavigationEnd, Router } from "@angular/router";
import { filter, Subscription } from "rxjs";
import { HSStaticMethods } from "preline/non-auto";
@Component({
selector: "app-root",
template: "<router-outlet />",
})
export class AppComponent implements OnInit, OnDestroy {
private subscription?: Subscription;
constructor(private router: Router) {}
ngOnInit() {
this.subscription = this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(() => {
queueMicrotask(() => {
HSStaticMethods.autoInit();
});
});
}
ngOnDestroy() {
this.subscription?.unsubscribe();
HSStaticMethods.cleanCollection();
}
}
The important part is not the exact hook name; it is the order. Angular must finish putting the route view into the DOM before Preline UI scans it.
For a full Preline UI installation, preline/non-auto is the safest Angular default. It gives you HSStaticMethods and plugin classes without depending on browser script or plugin auto-entry page-load timing.
import { HSStaticMethods } from "preline/non-auto";
HSStaticMethods.autoInit(["dropdown", "overlay"]);
HSStaticMethods.cleanCollection(["dropdown", "overlay"]);
If a component owns a single plugin root, importing the plugin class from preline/non-auto is more direct. It stays on the declared package surface while avoiding automatic page-load initialization.
import { HSDropdown } from "preline/non-auto";
HSDropdown.autoInit();
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 Angular feature 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". Use it for simple pages. In Angular components and routed apps, the /non-auto entry is easier to reason about because initialization stays aligned with Angular lifecycle hooks.
autoInit is good for route-level scans. Manual instances are better when an Angular component owns one specific plugin root and can destroy it directly in ngOnDestroy.
import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild } from "@angular/core";
import { HSDropdown, type IHTMLElementFloatingUI } from "preline/non-auto";
@Component({
selector: "app-dropdown",
template: `
<div #dropdownRoot class="hs-dropdown relative inline-flex">
...
</div>
`,
})
export class DropdownComponent implements AfterViewInit, OnDestroy {
@ViewChild("dropdownRoot", { static: true })
dropdownRoot!: ElementRef<HTMLElement>;
private dropdown?: InstanceType<typeof HSDropdown>;
ngAfterViewInit() {
this.dropdown = new HSDropdown(this.dropdownRoot.nativeElement as unknown as IHTMLElementFloatingUI);
}
ngOnDestroy() {
this.dropdown?.destroy();
}
}
In strict Angular builds, keep any package declaration workaround local and narrow instead of disabling type checking for the whole app. For example, if you intentionally use a plugin-specific full-package path such as preline/plugins/dropdown-non-auto before matching declaration files ship, declare only that module.
declare module "preline/plugins/dropdown-non-auto" {
const HSDropdown: {
new (el: HTMLElement, options?: Record<string, unknown>): {
destroy(): void;
};
autoInit(): void;
};
export default HSDropdown;
}
If your build reports a missing vanilla-calendar-pro/types declaration from the full preline/non-auto types, install the optional datepicker dependency or add a temporary declaration for that module. Keep that declaration close to your Preline integration so it is easy to remove when package types are updated.
Most core plugins do not use jQuery. Dropdowns, overlays, tooltips, popovers, tabs, and similar components use plain JavaScript. Positioning behavior uses @floating-ui/dom.
jQuery is only relevant 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, tabs, or tooltips.
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/non-auto in Angular when you need explicit lifecycle control.HSStaticMethods.autoInit() after Angular has rendered the component or route markup.NavigationEnd plus a deferred callback for routed Angular apps.ViewChild when a component owns one plugin root.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 React + Vite projects, including autoInit, module imports, cleanup, and optional dependencies.
View guideA practical guide to using Preline UI JavaScript plugins in Vue projects, including nextTick, autoInit, module imports, cleanup, and optional dependencies.
View guide