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

Install Preline UI with Hugo using Tailwind CSS

Install Preline UI with Tailwind CSS in Hugo projects, including JavaScript plugin setup, Hugo Pipes, static scripts, and optional dependencies.

Installation

Please note that the plugin has been tested with the 0.164.0 (extended) version of the framework. The framework was installed using the standard hugo new site <project-name> command.
This guide uses Hugo's native css.TailwindCSS and js.Build pipes (available since Hugo v0.128.0 and required since v0.161.0, when Hugo dropped support for the standalone Tailwind CSS binary). If you are using your own project structure or a different version, pay attention to the file paths and features of your version!

Hugo quick setup

Start with a working Hugo site, then add Tailwind CSS before importing Preline UI.

Some components rely on third-party libraries. The setup below assumes full Preline UI usage with those dependencies preloaded. If you do not plan to use those components, you can remove the related libraries from your configuration.

  1. Install Preline UI

    Install preline. Hugo Pipes (css.TailwindCSS, js.Build) reads packages directly from node_modules, so no manual copy into static/ is needed.

    Terminal
                              
                                npm install preline
                              
                            

    Preline UI uses the Tailwind CSS Forms plugin across form components. Install it if you have not already: npm install -D @tailwindcss/forms

  2. Set up Tailwind CSS

    Install Tailwind CSS and its CLI package. Hugo's native css.TailwindCSS pipe shells out to this CLI. As of Hugo v0.161.0, the standalone Tailwind CSS binary is no longer supported, so the CLI must come from npm.

    Terminal
                              
                                npm install tailwindcss @tailwindcss/cli
                              
                            
  3. Configure Hugo for Tailwind CSS

    css.TailwindCSS needs Hugo's build-stats feature enabled so Tailwind can see which classes your templates actually use (Tailwind can't scan compiled .html output the way it scans plain source files). Add this block to hugo.toml.

    hugo.toml
                              
                                [build]
                                  [build.buildStats]
                                    enable = true
                                  [[build.cachebusters]]
                                    source = 'assets/notwatching/hugo_stats\.json'
                                    target = 'css'
                                  [[build.cachebusters]]
                                    source = '(postcss|tailwind)\.config\.js'
                                    target = 'css'
    
                                [module]
                                  [[module.mounts]]
                                    source = 'assets'
                                    target = 'assets'
                                  [[module.mounts]]
                                    disableWatch = true
                                    source = 'hugo_stats.json'
                                    target = 'assets/notwatching/hugo_stats.json'
                              
                            
  4. Include Preline CSS

    Import Preline into projects_root_directory/assets/css/main.css.

    main.css
                              
                                @import "tailwindcss";
    
                                @import "preline/variants.css";
                                @source "node_modules/preline/dist/*.js";
                                @source "hugo_stats.json";
    
                                /* Optional Preline UI Datepicker Plugin */
                                /* @import "preline/datepicker-styles-utility.css"; */
    
                                /* Plugins */
                                /* @plugin "@tailwindcss/forms"; */
    
                                /* Preline Themes */
                                @import "preline/theme.css";
    
                                /* Optional Preline UI Datatable Plugin: DataTables.net renders its own
                                  search/length/paging controls alongside Preline's own; hide the native
                                  ones since they can't be disabled through DataTables options alone. */
                                /*
                                .dt-layout-row:has(.dt-search),
                                .dt-layout-row:has(.dt-length),
                                .dt-layout-row:has(.dt-paging) {
                                  display: none !important;
                                }
                                */
                              
                            

    @source paths here resolve relative to the project root, not relative to main.css's own location on disk, which is different from bundler-based setups (Vite, webpack), where @source resolves relative to the CSS file itself. Copying a ../../node_modules/...-style path from a non-Hugo example will silently match zero files: no error, no warning, and any class that only exists inside Preline's JS bundle (not in your rendered HTML) will quietly never make it into the compiled CSS.

    See the Theme docs to learn more about Preline Themes.

  5. Render the stylesheet with Hugo Pipes

    Instead of invoking the Tailwind CLI directly, call it through Hugo's native css.TailwindCSS function from a partial, so the stylesheet is built, minified in production, and fingerprinted automatically. Create layouts/_partials/css.html.

    layouts/_partials/css.html
                              
                                {{ with resources.Get "css/main.css" }}
                                  {{ $opts := dict "minify" (not hugo.IsDevelopment) "skipInlineImportsNotFound" true }}
                                  {{ with . | css.TailwindCSS $opts }}
                                    {{ if hugo.IsDevelopment }}
                                      <link rel="stylesheet" href="{{ .RelPermalink }}">
                                    {{ else }}
                                      {{ with . | fingerprint }}
                                        <link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
                                      {{ end }}
                                    {{ end }}
                                  {{ end }}
                                {{ end }}
                              
                            

    skipInlineImportsNotFound is required here: Hugo's own CSS import inliner doesn't understand bare npm-style specifiers like @import "preline/theme.css"; and fails the build with failed to resolve CSS @import if this option is left out. Setting it to true leaves those imports unresolved for Hugo's own inliner and lets the Tailwind CLI, which does understand node_modules resolution, handle them instead.

  6. Bundle the scripts with Hugo Pipes

    Create assets/js/preline.ts. Optional third-party libraries used by some plugins (Datatable, Datepicker, File Upload, Range Slider) must be assigned to window before the preline/non-auto import: Preline's HSStaticMethods builds its plugin availability map once, when that module is evaluated, so a library that lands on window afterwards will not retroactively enable its plugin. Only import the libraries the components you actually use need, and remove the rest.

    assets/js/preline.ts
                              
                                import $ from "jquery";
                                import _ from "lodash";
                                import Dropzone from "dropzone";
                                import noUiSlider from "nouislider";
    
                                window.$ = $;
                                window.jQuery = $;
                                window._ = _;
                                window.Dropzone = Dropzone;
                                window.noUiSlider = noUiSlider;
    
                                async function initPreline() {
                                  const dtModule = await import("datatables.net-dt");
                                  window.DataTable = dtModule.default ?? dtModule;
    
                                  const vcModule = await import("vanilla-calendar-pro");
                                  window.VanillaCalendarPro = vcModule.Calendar ?? vcModule.default ?? vcModule;
    
                                  const { HSStaticMethods } = await import("preline/non-auto");
                                  HSStaticMethods.autoInit();
                                }
    
                                if (document.readyState === "loading") {
                                  document.addEventListener("DOMContentLoaded", initPreline);
                                } else {
                                  initPreline();
                                }
                              
                            

    Create layouts/_partials/js.html to bundle it with esbuild via js.Build.

    layouts/_partials/js.html
                              
                                {{ with resources.Get "js/preline.ts" }}
                                  {{ $opts := dict "targetPath" "js/preline.js" "minify" (not hugo.IsDevelopment) }}
                                  {{ with . | js.Build $opts }}
                                    <script src="{{ .RelPermalink }}" type="module"></script>
                                  {{ end }}
                                {{ end }}
                              
                            

    Call both partials from layouts/baseof.html. Hugo's template system moved base layouts to the project root in v0.146.0, so use layouts/baseof.html, not layouts/_default/baseof.html. Wrap the CSS partial in templates.Defer: the build-stats content-detection from the previous steps needs that deferred render pass, and calling the partial directly will break class detection.

    layouts/baseof.html
                              
                            

    Datepicker's own availability check only verifies that vanilla-calendar-pro is present, but it does not check for lodash, even though the plugin uses it internally. Forgetting window._ will pass that check and then throw at runtime the moment a datepicker actually renders. File Upload's check does verify both lodash and Dropzone.

Optional Preline UI styles

Preline UI ships with a small set of opinionated base styles. If you want them in your project, add them to your CSS file. These defaults used to come bundled with Tailwind CSS v3, so they are still available as an optional layer in Preline UI.

CSS
                        
                          /* Adds pointer cursor to buttons */
                          @layer base {
                            button:not(:disabled),
                            [role="button"]:not(:disabled) {
                              cursor: pointer;
                            }
                          }

                          /* Defaults hover styles on all devices */
                          @custom-variant hover (&:hover);
                        
                      

© 2026 Preline Labs.