Razor views and pages only emit HTML, so Preline UI markup drops straight into a .cshtml file with the same Tailwind CSS classes you would use anywhere else. ASP.NET Core uses the standard class attribute, so the markup is identical to the plain HTML examples. The bundle your layout loads is what attaches the behavior once the page reaches the browser.
<div class="hs-dropdown relative inline-flex">
<button id="hs-dropdown-example" type="button" class="hs-dropdown-toggle ..." aria-haspopup="menu" aria-expanded="false" aria-label="Dropdown">
Actions
</button>
<div class="hs-dropdown-menu ... hidden" role="menu" aria-orientation="vertical" aria-labelledby="hs-dropdown-example">
...
</div>
</div>
ASP.NET Core Tag Helpers such as asp-controller, asp-action, and asp-page run on the server and render plain anchors and form attributes, so they sit alongside Preline UI classes without affecting client-side behavior. The one thing to watch in .cshtml is Razor's @ syntax: a single @ in markup starts a Razor expression, so escape a literal at-sign in your HTML as @@. Preline UI relies on data-* attributes and classes rather than @, so this only comes up when a value of your own contains an @ sign.
Markup you repeat across pages, or that carries real per-instance data, is a better fit for a Razor partial view with a typed model than for copy-pasted blocks. Keep the data in the page's PageModel (or the MVC action's view model), not hardcoded in the view.
namespace YourApp.Models;
public record AccordionItem(string Title, string Content);
Render the partial once per item, passing the model explicitly.
Some components take their configuration as JSON through a data-hs-* attribute, for example Datatable's data-hs-datatable or Datepicker's data-hs-datepicker. Build that JSON on the server with System.Text.Json.JsonSerializer.Serialize and assign it to a local variable instead of hand-writing a JSON string in the markup. Razor HTML-encodes the value when you print it, so the quotes inside the attribute stay valid without any manual escaping.
Page-level autoInit is good for most markup. A manual instance is better when one specific node needs explicit control, for example a menu you re-render through AJAX. Create the instance after the markup exists and guard it with getInstance so a re-run does not double-initialize the same node.
function initUserMenu() {
const el = document.querySelector("#user-menu");
if (el && !window.HSDropdown.getInstance(el)) {
new window.HSDropdown(el);
}
}
The getInstance guard keeps a single instance per node across updates. If a partial swap is allowed to replace the node, prefer page-level autoInit over a long-lived manual instance, since the element the reference points at can be swapped out.
Preline UI stores plugin instances in internal collections such as window.$hsDropdownCollection. That registry lets plugins coordinate in plain HTML, ASP.NET Core, and other environments without framework context.
A full navigation reloads the page and resets the registry, so plain MVC and Razor Pages need nothing extra. Partial view and AJAX updates replace markup without a reload, so a registry entry can linger as a stale reference when its node is swapped out. When you intentionally remove a region of Preline UI markup, clean the relevant collection before re-running autoInit so the registry only holds live nodes.
window.HSStaticMethods.cleanCollection("dropdown");
window.HSStaticMethods.cleanCollection(["dropdown", "overlay"]);
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, so you do not need to bring in noUiSlider CSS just to make Preline UI styling work.
File Upload depends on lodash and dropzone, and Datepicker depends on lodash. Load both before the Preline UI script, scoped to the view that uses the component rather than the shared layout. Datepicker also needs its Tailwind CSS grid utility classes: import the public preline/datepicker-styles-utility.css export alongside variants.css in your Tailwind source. It is separate from the aggregate variants.css import.
"Scoped to the view" needs one more adjustment in _Layout.cshtml. The default MVC and Razor Pages project templates render @RenderSectionAsync("Scripts") after the layout's own scripts, right before </body>. If the Preline UI script tag sits above that section in the default order, a page's @section Scripts block loads after Preline UI has already run its startup scan, so the optional dependency is not there yet and the component quietly fails to initialize. Move the section above the Preline UI script tag instead, so page-specific dependencies are always in place first.