Update v5.0 - Preline MCP, AI Prompts, Animated Icons and more. Visit Changelog

SolidJS

Using Preline UI with SolidJS

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.

Start with the Solid mental model

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.

Run autoInit after Solid mounts

Use onMount for browser-only initialization. Keep vendor setup in a cached helper so remounts rescan the DOM without repeating the dynamic-import chain.

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

Rescan after Solid Router changes

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.

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

Choose imports by the level of control you need

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.

preline.ts
                        
                          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.

dropdown.ts
                        
                          import { HSDropdown } from "preline/non-auto";

                          HSDropdown.autoInit();
                        
                      

Single plugin packages keep small Solid surfaces focused

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.

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

Use refs for manual plugin instances

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.

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

Cleanup matters because Preline UI keeps registries

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.

Cleanup
                        
                          onCleanup(() => {
                            dropdown?.destroy();
                          });

                          // Registry maintenance only; this does not call destroy().
                          HSStaticMethods.cleanCollection("dropdown");
                        
                      

Optional dependencies only matter for the plugins that use 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.

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.

Install only the integrations you use
                        
                          # 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
                        
                      
Add the matching blocks inside setupPreline()
                        
                          // 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.

Global CSS for Preline Datatable controls
                        
                          .dt-layout-row:has(.dt-search),
                          .dt-layout-row:has(.dt-length),
                          .dt-layout-row:has(.dt-paging) {
                            display: none !important;
                          }
                        
                      

The practical checklist

  • Use preline/non-auto in Solid when you need explicit lifecycle control.
  • Use a cached initializer and run HSStaticMethods.autoInit() in onMount after the target markup exists.
  • Use createEffect plus queueMicrotask for route rescans under @solidjs/router, then call the same cached initializer.
  • Use refs and destroy() for reusable components that own one plugin root.
  • Do not use cleanCollection() as a replacement for destroy(); it only clears registry entries.
  • Install optional third-party dependencies only for the plugins that need them, such as datatables.net-dt for Datatable, Vanilla Calendar and Lodash for Datepicker, noUiSlider for Range Slider, or Dropzone for File Upload.

© 2026 Preline Labs.