Using Preline UI with Svelte
A practical guide to using Preline UI JavaScript plugins in Svelte and SvelteKit projects, including onMount, tick, afterNavigate, actions, cleanup, and optional dependencies.
View guideUpdate v5.0 - Preline MCP, AI Prompts, Animated Icons and more. Visit Changelog
A practical guide to wiring Preline UI into SolidJS without fighting Solid's rendering model, refs, and router updates.
SolidJS and Preline UI fit together best when Solid owns rendering and Preline UI attaches behavior after the DOM is already in place. The important part is not a special Solid adapter. It is choosing the right Preline UI entry and putting initialization in Solid's lifecycle.
The current Preline UI package exposes preline/non-auto, named plugin classes, and single-plugin packages, so Solid can render first and initialize only the DOM nodes that are already committed.
Preline UI is a DOM-driven Tailwind CSS component system. Solid renders JSX to real DOM nodes; Preline UI scans those nodes and attaches behavior to matching markup. That is the same contract Preline UI uses in plain HTML, Astro, Vue, Svelte, Angular, React, Laravel, Rails, and similar stacks.
The tradeoff is explicit lifecycle work. Initialize after Solid has mounted the markup, rescan after route content changes, and destroy manually created instances when Solid removes their root element.
Use onMount for browser-only initialization. Keep vendor setup in a cached helper so remounts rescan the DOM without repeating the dynamic-import chain.
import { onMount } from "solid-js";
import initPreline from "../scripts/preline";
export default function DropdownSection() {
onMount(() => {
void initPreline().catch((error: unknown) => {
console.error("[preline] failed to initialize components", error);
});
});
return (
<div class="hs-dropdown relative inline-flex">
<button class="hs-dropdown-toggle" type="button">
Toggle
</button>
<div class="hs-dropdown-menu hidden">Menu</div>
</div>
);
}
This keeps Preline UI out of server rendering and avoids relying on browser script or plugin auto-entry page-load timing. Use a manual instance with destroy() when a reusable component must own cleanup locally.
Client-side navigation can replace route markup without a page reload. Put the rescan in a component that is rendered inside the router context, read location.pathname inside createEffect, then defer autoInit to the next microtask so Solid has committed the route DOM.
import { createEffect } from "solid-js";
import { useLocation } from "@solidjs/router";
import initPreline from "../scripts/preline";
export function PrelineRouterSync() {
const location = useLocation();
createEffect(() => {
location.pathname;
queueMicrotask(() => {
void initPreline().catch((error: unknown) => {
console.error("[preline] failed to initialize route", error);
});
});
});
return null;
}
In @solidjs/router, useLocation() must run inside a route context. Place this helper in a route layout or another component rendered under <Route>, not as a loose child outside routing.
Use this route-aware initializer instead of a separate one-time onMount initializer at the same layout level. The effect also runs for the initial route, so both are not needed.
For a full Preline UI installation, preline/non-auto is the most practical Solid default. It gives you HSStaticMethods and plugin classes without automatic initialization on page load.
import { HSStaticMethods } from "preline/non-auto";
HSStaticMethods.autoInit(["dropdown", "overlay"]);
If a surface only needs one class from the full package, import that class from preline/non-auto. This keeps strict TypeScript builds on the declared package surface.
import { HSDropdown } from "preline/non-auto";
HSDropdown.autoInit();
Preline UI publishes individual plugin packages such as @preline/dropdown, @preline/overlay, @preline/select, and @preline/range-slider. Use them when a small Solid surface needs only one or two interactive primitives instead of the full package.
npm install @preline/dropdown
// The root entry registers automatic initialization on window load.
import "@preline/dropdown";
The root entry is the package's auto-init bundle and is appropriate for simple client-rendered static pages. The package also ships non-auto.js and non-auto.mjs runtime bundles for manual initialization. In the current package metadata, TypeScript declarations are attached only to the root entry, so strict TypeScript projects need an explicit local declaration before importing a non-auto subpath. For routed Solid applications, the full package's declared preline/non-auto entry and cached initializer from the installation guide provide the cleanest typed lifecycle integration.
autoInit is convenient for page-level scans. A manual instance is better when one Solid component owns one plugin root and can destroy it locally.
import { onCleanup, onMount } from "solid-js";
import { HSDropdown, type IHTMLElementFloatingUI } from "preline/non-auto";
export default function Dropdown() {
let dropdownRoot!: HTMLDivElement;
let dropdown: InstanceType<typeof HSDropdown> | undefined;
onMount(() => {
dropdown = new HSDropdown(dropdownRoot as unknown as IHTMLElementFloatingUI);
});
onCleanup(() => {
dropdown?.destroy();
});
return (
<div ref={dropdownRoot} class="hs-dropdown relative inline-flex">
<button class="hs-dropdown-toggle" type="button">
Toggle
</button>
<div class="hs-dropdown-menu hidden">Menu</div>
</div>
);
}
Preline UI stores plugin instances in internal collections such as window.$hsDropdownCollection. That registry lets plugins coordinate across plain HTML and framework environments without requiring framework context.
In Solid, call destroy() for manual instances before their root elements are removed. cleanCollection() only clears Preline's registry entries; it does not destroy instances or remove their event listeners, so it is not a substitute for instance cleanup.
onCleanup(() => {
dropdown?.destroy();
});
// Registry maintenance only; this does not call destroy().
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.
The Preline Datatable integration expects global $, jQuery, and DataTable values. Use the compatible jquery@3.7.1 and datatables.net-dt@2.3.8 packages shown below. These dependencies do 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.
Datepicker uses Vanilla Calendar and Lodash, while File Upload uses Dropzone. Register these vendor globals before calling autoInit(); otherwise one failing plugin can interrupt initialization of components that follow it.
# Datatable
npm install jquery@3.7.1 datatables.net-dt@2.3.8
npm install -D @types/jquery
# Datepicker
npm install lodash@4.18.1 vanilla-calendar-pro@3.1.0
npm install -D @types/lodash
# Range Slider
npm install nouislider@15.8.1
# File Upload
npm install dropzone@6.0.0-beta.2
// Datatable
const jquery = await import("jquery");
const $ = jquery.default ?? jquery;
Object.assign(window, { $, jQuery: $ });
const datatables = await import("datatables.net-dt");
Object.assign(window, { DataTable: datatables.default ?? datatables });
// Datepicker
const lodash = await import("lodash");
Object.assign(window, { _: lodash.default ?? lodash });
const calendar = await import("vanilla-calendar-pro");
Object.assign(window, { VanillaCalendarPro: calendar.Calendar });
// Range Slider
const slider = await import("nouislider");
Object.assign(window, { noUiSlider: slider.default ?? slider });
// File Upload
const dropzone = await import("dropzone");
Object.assign(window, { Dropzone: dropzone.default ?? dropzone });
// Keep this import after every vendor global used by the page.
return (await import("preline/non-auto")).HSStaticMethods;
When a Datatable uses Preline's custom pagination markup, hide the search, length, and paging rows generated by DataTables so users do not see duplicate controls.
.dt-layout-row:has(.dt-search),
.dt-layout-row:has(.dt-length),
.dt-layout-row:has(.dt-paging) {
display: none !important;
}
preline/non-auto in Solid when you need explicit lifecycle control.HSStaticMethods.autoInit() in onMount after the target markup exists.createEffect plus queueMicrotask for route rescans under @solidjs/router, then call the same cached initializer.destroy() for reusable components that own one plugin root.cleanCollection() as a replacement for destroy(); it only clears registry entries.datatables.net-dt for Datatable, Vanilla Calendar and Lodash for Datepicker, noUiSlider for Range Slider, or Dropzone for File Upload.A practical guide to using Preline UI JavaScript plugins in Svelte and SvelteKit projects, including onMount, tick, afterNavigate, actions, cleanup, and optional dependencies.
View guideA practical guide to using Preline UI JavaScript plugins in Qwik projects, including visible tasks, Qwik City navigation, module imports, cleanup, and optional dependencies.
View guide