Preline UI, Tailwind CSS components without a framework
Preline UI is a set of interface components built on Tailwind CSS where behaviour is driven by attributes in markup rather than by writing code. The current version is 4.2.0. The library works without any framework, with wrappers for popular ecosystems offered separately.
Attribute driven, the defining trait
The approach used here was standard in libraries predating the framework era and returned to favour alongside server side rendering.
Instead of importing a component and passing it props, you add an attribute to markup describing what that element should be. An element carrying the right attribute becomes a trigger opening a dialog, another becomes that dialog, and the library binds them by identifier. All the interaction code sits in one script loaded once.
The gain is clearest where no component layer exists: a server rendered page, a template in a templating language, a panel bolted onto an existing application. You do not have to introduce a framework merely to get a working modal.
The price is losing what a component layer provides: typing, editor completion, and catching errors before runtime. A typo in an attribute name reports nothing at all, the element simply does nothing. On a larger project it pays to wrap this in your own components, turning a typo into a compile error.
Reinitialising after navigation, the most common problem
This is what most people combining the library with a single page application trip over, and it is worth stating plainly, because the symptoms mislead.
The library scans the document for elements carrying the right attributes and attaches event handling to them. It does that once, on page load. If your application swaps content without reloading the document, new elements are never found and never work.
The symptom looks like this: a component works after a refresh and stops working after navigating to that page from another. It is easy to read as a library bug when it is a lifecycle matter. The fix is rerunning the scan after every view change, which in Next.js reduces to calling it in an effect keyed on the route.
A second form of the same problem concerns asynchronously loaded content. A list fetched from a server and inserted after the first render also needs reinitialising, because it did not exist during the first scan.
Watch out for the opposite extreme, though, meaning calling initialisation on every component render. Scanning the whole document is not free, and attaching handlers to the same element repeatedly leads to behaviour such as a dialog opening and closing immediately. A sensible arrangement is one call after navigation completes and one after inserting server fetched content, not one per state change.
When this is the right choice
The scenarios are worth naming plainly, because this library targets a specific situation and loses to the alternatives outside it.
It works best where no component layer exists and you do not intend to introduce one. A server rendered application, an admin panel bolted onto an existing system, a page in a templating language. You get working components without adding a framework and without a client side build step.
The second good scenario is a prototype or short lived project where pace matters more than architecture. Paste the markup, add the script, and you have a working interface within an hour.
It fares worse inside a large component driven application, where attribute driven behaviour clashes with the rest of the code. In such a project interface state lives in components while the library keeps it in its own structures bound to the document tree, so the two worlds have to be synchronised by hand. That works, but it is a source of bugs that would not exist with a solution written for the same model.
A third case worth looking further for is a project with hard accessibility requirements, covered below.
Licensing, worth checking before deployment
A correction is needed here, because the ubiquitous description of this as MIT licensed is incomplete.
The project uses dual licensing: a user may choose MIT or a separate licence the authors call fair use. The second adds restrictions MIT does not carry, chiefly a prohibition on building a product competing with the library itself and a requirement to preserve licensing notices on redistribution.
The practical significance for most teams is small, since nobody is building a competing component library, and the MIT option remains available. One technical consequence is worth knowing, though: the licence file is non standard, so automated dependency scanners flag it as unrecognised. In an organisation where such scanning is part of the process, that means an extra question from legal rather than an automatic pass.
The recommendation is therefore simple: if your company requires dependency licences to be approved, raise it early and point out that the MIT option is among the permitted choices. Discovering this during a pre deployment audit is considerably less convenient.
What is Preline UI?
Preline UI is an open-source component library built on Tailwind CSS, offering over 640 ready-to-use UI components in the free tier. It is one of the richest free Tailwind libraries, providing both simple elements like buttons and forms, as well as complex components like carousels, steppers, and datepickers.
Preline UI stands out with its exceptionally rich documentation featuring hundreds of examples, making it an ideal library for developers looking for ready-made solutions without having to write code from scratch. All components are fully responsive, support dark mode, and are optimized for accessibility.
Why Preline UI?
- 640+ components - One of the largest free collections
- Excellent documentation - Hundreds of examples with code
- Vanilla JS + React + Vue - Support for popular frameworks
- Dark mode - Built-in dark theme support
- Copy-paste - Copy the code and use it right away
- Dual licence - MIT or the Preline Fair Use Licence, free for commercial use
Preline UI vs other libraries
| Feature | Preline UI | Flowbite | daisyUI | Tailwind Plus |
|---|---|---|---|---|
| Price | free; Pro $249, Team $459, Enterprise on request | free plus a Pro tier | free | $299 for one person, $979 for a team of up to 25 |
| Components | 640+ in the free tier | 68 component types | 68 components | 500+ UI blocks |
| Blocks and templates | 204 blocks and 5 templates free, 780+ blocks and 21 templates in Pro | 400+ sections in Flowbite Blocks | none | templates in React and Next.js |
| Frameworks | HTML, React, Vue | HTML plus framework wrappers | CSS only | HTML, React, Vue |
| Documentation | excellent | very good | good | great |
| Dark mode | built-in | built-in | built-in | built-in |
| License | dual, MIT or fair use | MIT | MIT | commercial, paid |
When to choose Preline UI?
Choose Preline UI when:
- You need a large number of free components
- You value good documentation with examples
- You are building an HTML, React, or Vue site
- You need advanced components (stepper, tree view)
- You want to prototype quickly
Consider alternatives when:
- You need Angular/Svelte support (Flowbite)
- You prefer CSS-only without JavaScript (DaisyUI)
- Your budget allows for a paid solution (Tailwind Plus)
Installation
NPM/Yarn
# npm
npm install preline
# yarn
yarn add preline
# pnpm
pnpm add prelineTailwind CSS configuration
Since Tailwind 4 the configuration lives in the stylesheet rather than in a tailwind.config.js file:
/* main.css */
@import "tailwindcss";
/* Preline UI */
@source "./node_modules/preline/dist/*.js";
@import "./node_modules/preline/variants.css";Adding the JavaScript script
<!-- Before </body> -->
<script src="./node_modules/preline/dist/preline.js"></script>
<!-- Or from CDN -->
<script src="https://cdn.jsdelivr.net/npm/preline@4.2.0/dist/preline.js"></script>React/Next.js setup
// app/layout.tsx or pages/_app.tsx
'use client'
import { useEffect } from 'react'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
useEffect(() => {
import('preline')
}, [])
return (
<html lang="pl">
<body>{children}</body>
</html>
)
}Vue setup
// main.js
import 'preline'
// Or in a component
import { onMounted } from 'vue'
onMounted(() => {
import('preline')
})Components - Accordion
Accordion allows you to expand and collapse content sections.
<!-- Basic Accordion -->
<div class="hs-accordion-group">
<div class="hs-accordion active" id="hs-basic-heading-one">
<button class="hs-accordion-toggle hs-accordion-active:text-blue-600 py-3 inline-flex items-center gap-x-3 w-full font-semibold text-start text-gray-800 hover:text-gray-500 focus:outline-none focus:text-gray-500 rounded-lg disabled:opacity-50 disabled:pointer-events-none dark:hs-accordion-active:text-blue-500 dark:text-neutral-200 dark:hover:text-neutral-400 dark:focus:text-neutral-400" aria-expanded="true" aria-controls="hs-basic-collapse-one">
<svg class="hs-accordion-active:hidden block size-3.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
<svg class="hs-accordion-active:block hidden size-3.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
</svg>
Accordion #1
</button>
<div id="hs-basic-collapse-one" class="hs-accordion-content w-full overflow-hidden transition-[height] duration-300" role="region" aria-labelledby="hs-basic-heading-one">
<p class="text-gray-800 dark:text-neutral-200">
This is the first accordion body. It is shown by default.
</p>
</div>
</div>
<div class="hs-accordion" id="hs-basic-heading-two">
<button class="hs-accordion-toggle hs-accordion-active:text-blue-600 py-3 inline-flex items-center gap-x-3 w-full font-semibold text-start text-gray-800 hover:text-gray-500 focus:outline-none focus:text-gray-500 rounded-lg disabled:opacity-50 disabled:pointer-events-none dark:hs-accordion-active:text-blue-500 dark:text-neutral-200 dark:hover:text-neutral-400 dark:focus:text-neutral-400" aria-expanded="false" aria-controls="hs-basic-collapse-two">
<svg class="hs-accordion-active:hidden block size-3.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
<svg class="hs-accordion-active:block hidden size-3.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
</svg>
Accordion #2
</button>
<div id="hs-basic-collapse-two" class="hs-accordion-content hidden w-full overflow-hidden transition-[height] duration-300" role="region" aria-labelledby="hs-basic-heading-two">
<p class="text-gray-800 dark:text-neutral-200">
This is the second accordion body.
</p>
</div>
</div>
</div>Accordion with icons on the right side
<div class="hs-accordion-group">
<div class="hs-accordion active bg-white border -mt-px first:rounded-t-lg last:rounded-b-lg dark:bg-neutral-800 dark:border-neutral-700" id="hs-bordered-heading-one">
<button class="hs-accordion-toggle hs-accordion-active:text-blue-600 inline-flex items-center justify-between gap-x-3 w-full font-semibold text-start text-gray-800 py-4 px-5 hover:text-gray-500 disabled:opacity-50 disabled:pointer-events-none dark:hs-accordion-active:text-blue-500 dark:text-neutral-200 dark:hover:text-neutral-400" aria-expanded="true" aria-controls="hs-bordered-collapse-one">
Accordion #1
<svg class="hs-accordion-active:hidden block size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"></path>
</svg>
<svg class="hs-accordion-active:block hidden size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m18 15-6-6-6 6"></path>
</svg>
</button>
<div id="hs-bordered-collapse-one" class="hs-accordion-content w-full overflow-hidden transition-[height] duration-300" role="region" aria-labelledby="hs-bordered-heading-one">
<div class="pb-4 px-5">
<p class="text-gray-800 dark:text-neutral-200">
Content for accordion #1
</p>
</div>
</div>
</div>
</div>Components - Tabs
<!-- Basic Tabs -->
<div class="border-b border-gray-200 dark:border-neutral-700">
<nav class="flex gap-x-1" aria-label="Tabs" role="tablist" aria-orientation="horizontal">
<button type="button" class="hs-tab-active:font-semibold hs-tab-active:border-blue-600 hs-tab-active:text-blue-600 py-4 px-1 inline-flex items-center gap-x-2 border-b-2 border-transparent text-sm whitespace-nowrap text-gray-500 hover:text-blue-600 focus:outline-none focus:text-blue-600 disabled:opacity-50 disabled:pointer-events-none dark:text-neutral-400 dark:hover:text-blue-500 active" id="tabs-with-underline-item-1" aria-selected="true" data-hs-tab="#tabs-with-underline-1" aria-controls="tabs-with-underline-1" role="tab">
Tab 1
</button>
<button type="button" class="hs-tab-active:font-semibold hs-tab-active:border-blue-600 hs-tab-active:text-blue-600 py-4 px-1 inline-flex items-center gap-x-2 border-b-2 border-transparent text-sm whitespace-nowrap text-gray-500 hover:text-blue-600 focus:outline-none focus:text-blue-600 disabled:opacity-50 disabled:pointer-events-none dark:text-neutral-400 dark:hover:text-blue-500" id="tabs-with-underline-item-2" aria-selected="false" data-hs-tab="#tabs-with-underline-2" aria-controls="tabs-with-underline-2" role="tab">
Tab 2
</button>
<button type="button" class="hs-tab-active:font-semibold hs-tab-active:border-blue-600 hs-tab-active:text-blue-600 py-4 px-1 inline-flex items-center gap-x-2 border-b-2 border-transparent text-sm whitespace-nowrap text-gray-500 hover:text-blue-600 focus:outline-none focus:text-blue-600 disabled:opacity-50 disabled:pointer-events-none dark:text-neutral-400 dark:hover:text-blue-500" id="tabs-with-underline-item-3" aria-selected="false" data-hs-tab="#tabs-with-underline-3" aria-controls="tabs-with-underline-3" role="tab">
Tab 3
</button>
</nav>
</div>
<div class="mt-3">
<div id="tabs-with-underline-1" role="tabpanel" aria-labelledby="tabs-with-underline-item-1">
<p class="text-gray-500 dark:text-neutral-400">
This is the <em class="font-semibold text-gray-800 dark:text-neutral-200">first</em> tab content.
</p>
</div>
<div id="tabs-with-underline-2" class="hidden" role="tabpanel" aria-labelledby="tabs-with-underline-item-2">
<p class="text-gray-500 dark:text-neutral-400">
This is the <em class="font-semibold text-gray-800 dark:text-neutral-200">second</em> tab content.
</p>
</div>
<div id="tabs-with-underline-3" class="hidden" role="tabpanel" aria-labelledby="tabs-with-underline-item-3">
<p class="text-gray-500 dark:text-neutral-400">
This is the <em class="font-semibold text-gray-800 dark:text-neutral-200">third</em> tab content.
</p>
</div>
</div>Tabs as pills (buttons)
<nav class="flex gap-x-1" aria-label="Tabs" role="tablist" aria-orientation="horizontal">
<button type="button" class="hs-tab-active:bg-blue-600 hs-tab-active:text-white py-3 px-4 inline-flex items-center gap-x-2 bg-transparent text-sm font-medium text-center text-gray-500 rounded-lg hover:text-blue-600 focus:outline-none focus:text-blue-600 disabled:opacity-50 disabled:pointer-events-none dark:text-neutral-400 dark:hover:text-blue-500 active" id="pills-item-1" aria-selected="true" data-hs-tab="#pills-1" aria-controls="pills-1" role="tab">
Tab 1
</button>
<button type="button" class="hs-tab-active:bg-blue-600 hs-tab-active:text-white py-3 px-4 inline-flex items-center gap-x-2 bg-transparent text-sm font-medium text-center text-gray-500 rounded-lg hover:text-blue-600 focus:outline-none focus:text-blue-600 disabled:opacity-50 disabled:pointer-events-none dark:text-neutral-400 dark:hover:text-blue-500" id="pills-item-2" aria-selected="false" data-hs-tab="#pills-2" aria-controls="pills-2" role="tab">
Tab 2
</button>
<button type="button" class="hs-tab-active:bg-blue-600 hs-tab-active:text-white py-3 px-4 inline-flex items-center gap-x-2 bg-transparent text-sm font-medium text-center text-gray-500 rounded-lg hover:text-blue-600 focus:outline-none focus:text-blue-600 disabled:opacity-50 disabled:pointer-events-none dark:text-neutral-400 dark:hover:text-blue-500" id="pills-item-3" aria-selected="false" data-hs-tab="#pills-3" aria-controls="pills-3" role="tab">
Tab 3
</button>
</nav>Components - Modal
<!-- Trigger Button -->
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none" aria-haspopup="dialog" aria-expanded="false" aria-controls="hs-basic-modal" data-hs-overlay="#hs-basic-modal">
Open modal
</button>
<!-- Modal -->
<div id="hs-basic-modal" class="hs-overlay hidden size-full fixed top-0 start-0 z-[80] overflow-x-hidden overflow-y-auto pointer-events-none" role="dialog" tabindex="-1" aria-labelledby="hs-basic-modal-label">
<div class="hs-overlay-open:mt-7 hs-overlay-open:opacity-100 hs-overlay-open:duration-500 mt-0 opacity-0 ease-out transition-all sm:max-w-lg sm:w-full m-3 sm:mx-auto">
<div class="flex flex-col bg-white border shadow-sm rounded-xl pointer-events-auto dark:bg-neutral-800 dark:border-neutral-700 dark:shadow-neutral-700/70">
<div class="flex justify-between items-center py-3 px-4 border-b dark:border-neutral-700">
<h3 id="hs-basic-modal-label" class="font-bold text-gray-800 dark:text-white">
Modal title
</h3>
<button type="button" class="size-8 inline-flex justify-center items-center gap-x-2 rounded-full border border-transparent bg-gray-100 text-gray-800 hover:bg-gray-200 focus:outline-none focus:bg-gray-200 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:text-neutral-400 dark:focus:bg-neutral-600" aria-label="Close" data-hs-overlay="#hs-basic-modal">
<span class="sr-only">Close</span>
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
</div>
<div class="p-4 overflow-y-auto">
<p class="text-gray-800 dark:text-neutral-400">
This is a modal body. You can add any content here.
</p>
</div>
<div class="flex justify-end items-center gap-x-2 py-3 px-4 border-t dark:border-neutral-700">
<button type="button" class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-sm hover:bg-gray-50 focus:outline-none focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700" data-hs-overlay="#hs-basic-modal">
Close
</button>
<button type="button" class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none">
Save changes
</button>
</div>
</div>
</div>
</div>Modal sizes
<!-- Small Modal -->
<div id="hs-small-modal" class="hs-overlay hidden">
<div class="hs-overlay-open:mt-7 hs-overlay-open:opacity-100 mt-0 opacity-0 ease-out transition-all sm:max-w-sm sm:w-full m-3 sm:mx-auto">
<!-- content -->
</div>
</div>
<!-- Large Modal -->
<div id="hs-large-modal" class="hs-overlay hidden">
<div class="hs-overlay-open:mt-7 hs-overlay-open:opacity-100 mt-0 opacity-0 ease-out transition-all lg:max-w-4xl lg:w-full m-3 lg:mx-auto">
<!-- content -->
</div>
</div>
<!-- Full Screen Modal -->
<div id="hs-full-screen-modal" class="hs-overlay hidden">
<div class="hs-overlay-open:mt-7 hs-overlay-open:opacity-100 mt-0 opacity-0 ease-out transition-all max-w-full w-full m-3 mx-auto h-[calc(100%-3.5rem)]">
<!-- content -->
</div>
</div>Components - Dropdown
<div class="hs-dropdown relative inline-flex">
<button id="hs-dropdown-default" type="button" class="hs-dropdown-toggle py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-sm hover:bg-gray-50 focus:outline-none focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700" aria-haspopup="menu" aria-expanded="false" aria-label="Dropdown">
Actions
<svg class="hs-dropdown-open:rotate-180 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"/>
</svg>
</button>
<div class="hs-dropdown-menu transition-[opacity,margin] duration hs-dropdown-open:opacity-100 opacity-0 hidden min-w-60 bg-white shadow-md rounded-lg mt-2 dark:bg-neutral-800 dark:border dark:border-neutral-700" role="menu" aria-orientation="vertical" aria-labelledby="hs-dropdown-default">
<div class="p-1 space-y-0.5">
<a class="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700" href="#">
Newsletter
</a>
<a class="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700" href="#">
Purchases
</a>
<a class="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700" href="#">
Downloads
</a>
<a class="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700" href="#">
Team Account
</a>
</div>
</div>
</div>Dropdown with icons
<div class="hs-dropdown relative inline-flex">
<button id="hs-dropdown-with-icons" type="button" class="hs-dropdown-toggle py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-sm hover:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700">
With icons
<svg class="hs-dropdown-open:rotate-180 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"/>
</svg>
</button>
<div class="hs-dropdown-menu transition-[opacity,margin] duration hs-dropdown-open:opacity-100 opacity-0 hidden min-w-60 bg-white shadow-md rounded-lg mt-2 divide-y divide-gray-200 dark:bg-neutral-800 dark:border dark:border-neutral-700 dark:divide-neutral-700">
<div class="p-1 space-y-0.5">
<a class="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300" href="#">
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" x2="12" y1="15" y2="3"/>
</svg>
Downloads
</a>
<a class="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300" href="#">
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
Team Account
</a>
</div>
<div class="p-1 space-y-0.5">
<a class="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-red-600 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 dark:text-red-500 dark:hover:bg-neutral-700" href="#">
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"/>
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/>
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/>
<line x1="10" x2="10" y1="11" y2="17"/>
<line x1="14" x2="14" y1="11" y2="17"/>
</svg>
Delete
</a>
</div>
</div>
</div>Components - Tooltip
<!-- Basic Tooltip -->
<div class="hs-tooltip inline-block">
<button type="button" class="hs-tooltip-toggle py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-sm hover:bg-gray-50 focus:outline-none focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700">
Hover me
</button>
<span class="hs-tooltip-content hs-tooltip-shown:opacity-100 hs-tooltip-shown:visible opacity-0 transition-opacity inline-block absolute invisible z-10 py-1 px-2 bg-gray-900 text-xs font-medium text-white rounded shadow-sm dark:bg-neutral-700" role="tooltip">
Tooltip content
</span>
</div>
<!-- Tooltip with different positions -->
<div class="hs-tooltip inline-block" data-hs-tooltip-placement="top">
<button type="button" class="hs-tooltip-toggle ...">Top</button>
<span class="hs-tooltip-content ..." role="tooltip">Top tooltip</span>
</div>
<div class="hs-tooltip inline-block" data-hs-tooltip-placement="bottom">
<button type="button" class="hs-tooltip-toggle ...">Bottom</button>
<span class="hs-tooltip-content ..." role="tooltip">Bottom tooltip</span>
</div>
<div class="hs-tooltip inline-block" data-hs-tooltip-placement="left">
<button type="button" class="hs-tooltip-toggle ...">Left</button>
<span class="hs-tooltip-content ..." role="tooltip">Left tooltip</span>
</div>
<div class="hs-tooltip inline-block" data-hs-tooltip-placement="right">
<button type="button" class="hs-tooltip-toggle ...">Right</button>
<span class="hs-tooltip-content ..." role="tooltip">Right tooltip</span>
</div>Components - Carousel
<div data-hs-carousel='{
"loadingClasses": "opacity-0",
"isAutoPlay": true
}' class="relative">
<div class="hs-carousel relative overflow-hidden w-full min-h-96 bg-white rounded-lg">
<div class="hs-carousel-body absolute top-0 bottom-0 start-0 flex flex-nowrap transition-transform duration-700 opacity-0">
<div class="hs-carousel-slide">
<div class="flex justify-center h-full bg-gray-100 p-6 dark:bg-neutral-900">
<span class="self-center text-4xl text-gray-800 transition duration-700 dark:text-white">First slide</span>
</div>
</div>
<div class="hs-carousel-slide">
<div class="flex justify-center h-full bg-gray-200 p-6 dark:bg-neutral-800">
<span class="self-center text-4xl text-gray-800 transition duration-700 dark:text-white">Second slide</span>
</div>
</div>
<div class="hs-carousel-slide">
<div class="flex justify-center h-full bg-gray-300 p-6 dark:bg-neutral-700">
<span class="self-center text-4xl text-gray-800 transition duration-700 dark:text-white">Third slide</span>
</div>
</div>
</div>
</div>
<!-- Prev/Next buttons -->
<button type="button" class="hs-carousel-prev hs-carousel-disabled:opacity-50 disabled:pointer-events-none absolute inset-y-0 start-0 inline-flex justify-center items-center w-[46px] h-full text-gray-800 hover:bg-gray-800/10 focus:outline-none focus:bg-gray-800/10 rounded-s-lg dark:text-white dark:hover:bg-white/10 dark:focus:bg-white/10">
<span class="text-2xl" aria-hidden="true">
<svg class="shrink-0 size-5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m15 18-6-6 6-6"></path>
</svg>
</span>
<span class="sr-only">Previous</span>
</button>
<button type="button" class="hs-carousel-next hs-carousel-disabled:opacity-50 disabled:pointer-events-none absolute inset-y-0 end-0 inline-flex justify-center items-center w-[46px] h-full text-gray-800 hover:bg-gray-800/10 focus:outline-none focus:bg-gray-800/10 rounded-e-lg dark:text-white dark:hover:bg-white/10 dark:focus:bg-white/10">
<span class="sr-only">Next</span>
<span class="text-2xl" aria-hidden="true">
<svg class="shrink-0 size-5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m9 18 6-6-6-6"></path>
</svg>
</span>
</button>
<!-- Pagination -->
<div class="hs-carousel-pagination flex justify-center absolute bottom-3 start-0 end-0 space-x-2">
<span class="hs-carousel-active:bg-blue-700 hs-carousel-active:border-blue-700 size-3 border border-gray-400 rounded-full cursor-pointer dark:border-neutral-600 dark:hs-carousel-active:bg-blue-500 dark:hs-carousel-active:border-blue-500"></span>
<span class="hs-carousel-active:bg-blue-700 hs-carousel-active:border-blue-700 size-3 border border-gray-400 rounded-full cursor-pointer dark:border-neutral-600 dark:hs-carousel-active:bg-blue-500 dark:hs-carousel-active:border-blue-500"></span>
<span class="hs-carousel-active:bg-blue-700 hs-carousel-active:border-blue-700 size-3 border border-gray-400 rounded-full cursor-pointer dark:border-neutral-600 dark:hs-carousel-active:bg-blue-500 dark:hs-carousel-active:border-blue-500"></span>
</div>
</div>Components - Stepper
<!-- Basic Stepper -->
<ul class="relative flex flex-row gap-x-2">
<!-- Completed Step -->
<li class="shrink basis-0 flex-1 group">
<div class="min-w-7 min-h-7 w-full inline-flex items-center text-xs align-middle">
<span class="size-7 flex justify-center items-center shrink-0 bg-blue-600 font-medium text-white rounded-full dark:bg-blue-500">
<svg class="shrink-0 size-3" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</span>
<div class="ms-2 w-full h-px flex-1 bg-blue-600 group-last:hidden dark:bg-blue-500"></div>
</div>
<div class="mt-3">
<span class="block text-sm font-medium text-gray-800 dark:text-white">
Step
</span>
</div>
</li>
<!-- Current Step -->
<li class="shrink basis-0 flex-1 group">
<div class="min-w-7 min-h-7 w-full inline-flex items-center text-xs align-middle">
<span class="size-7 flex justify-center items-center shrink-0 bg-blue-600 font-medium text-white rounded-full dark:bg-blue-500">
2
</span>
<div class="ms-2 w-full h-px flex-1 bg-gray-200 group-last:hidden dark:bg-neutral-700"></div>
</div>
<div class="mt-3">
<span class="block text-sm font-medium text-blue-600 dark:text-blue-500">
Step
</span>
</div>
</li>
<!-- Future Step -->
<li class="shrink basis-0 flex-1 group">
<div class="min-w-7 min-h-7 w-full inline-flex items-center text-xs align-middle">
<span class="size-7 flex justify-center items-center shrink-0 bg-gray-100 font-medium text-gray-800 rounded-full dark:bg-neutral-700 dark:text-white">
3
</span>
<div class="ms-2 w-full h-px flex-1 bg-gray-200 group-last:hidden dark:bg-neutral-700"></div>
</div>
<div class="mt-3">
<span class="block text-sm font-medium text-gray-800 dark:text-white">
Step
</span>
</div>
</li>
</ul>Vertical stepper
<ul class="relative flex flex-col gap-2">
<li class="flex items-center gap-x-2 shrink basis-0 flex-1 group">
<span class="min-w-7 min-h-7 inline-flex justify-center items-center text-xs align-middle">
<span class="size-7 flex justify-center items-center shrink-0 bg-blue-600 font-medium text-white rounded-full dark:bg-blue-500">
1
</span>
</span>
<span class="text-sm font-medium text-gray-800 dark:text-white">
First step completed
</span>
</li>
<li class="flex items-center gap-x-2 shrink basis-0 flex-1 group">
<span class="min-w-7 min-h-7 inline-flex justify-center items-center text-xs align-middle">
<span class="size-7 flex justify-center items-center shrink-0 bg-blue-600 font-medium text-white rounded-full dark:bg-blue-500">
2
</span>
</span>
<span class="text-sm font-medium text-blue-600 dark:text-blue-500">
Current step
</span>
</li>
<li class="flex items-center gap-x-2 shrink basis-0 flex-1 group">
<span class="min-w-7 min-h-7 inline-flex justify-center items-center text-xs align-middle">
<span class="size-7 flex justify-center items-center shrink-0 bg-gray-100 font-medium text-gray-800 rounded-full dark:bg-neutral-700 dark:text-white">
3
</span>
</span>
<span class="text-sm font-medium text-gray-800 dark:text-white">
Final step
</span>
</li>
</ul>Components - Form elements
Input
<!-- Basic Input -->
<div class="max-w-sm">
<label for="input-label" class="block text-sm font-medium mb-2 dark:text-white">Email</label>
<input type="email" id="input-label" class="py-3 px-4 block w-full border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600" placeholder="you@example.com">
</div>
<!-- Input with helper text -->
<div class="max-w-sm">
<label for="input-label-with-helper-text" class="block text-sm font-medium mb-2 dark:text-white">Email</label>
<input type="email" id="input-label-with-helper-text" class="py-3 px-4 block w-full border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600" placeholder="you@example.com" aria-describedby="hs-input-helper-text">
<p class="text-sm text-gray-500 mt-2" id="hs-input-helper-text">We'll never share your details.</p>
</div>
<!-- Input with validation -->
<div class="max-w-sm">
<label for="with-validation" class="block text-sm font-medium mb-2 dark:text-white">Email</label>
<input type="email" id="with-validation" class="py-3 px-4 block w-full border-red-500 rounded-lg text-sm focus:border-red-500 focus:ring-red-500 dark:bg-neutral-900 dark:border-red-600 dark:text-neutral-400" aria-describedby="email-error">
<p class="text-sm text-red-600 mt-2" id="email-error">Please enter a valid email address.</p>
</div>
<!-- Input with success -->
<div class="max-w-sm">
<label for="with-success" class="block text-sm font-medium mb-2 dark:text-white">Email</label>
<input type="email" id="with-success" class="py-3 px-4 block w-full border-teal-500 rounded-lg text-sm focus:border-teal-500 focus:ring-teal-500 dark:bg-neutral-900 dark:border-teal-600 dark:text-neutral-400" aria-describedby="email-success">
<p class="text-sm text-teal-600 mt-2" id="email-success">Looks good!</p>
</div>Select
<div class="max-w-sm">
<label for="countries" class="block text-sm font-medium mb-2 dark:text-white">Country</label>
<select id="countries" class="py-3 px-4 pe-9 block w-full border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600">
<option selected>Open this select menu</option>
<option value="US">United States</option>
<option value="CA">Canada</option>
<option value="FR">France</option>
</select>
</div>Checkbox
<div class="flex">
<input type="checkbox" class="shrink-0 mt-0.5 border-gray-200 rounded text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="hs-default-checkbox">
<label for="hs-default-checkbox" class="text-sm text-gray-500 ms-3 dark:text-neutral-400">Default checkbox</label>
</div>Radio
<div class="flex gap-x-6">
<div class="flex">
<input type="radio" name="radio-group" class="shrink-0 mt-0.5 border-gray-200 rounded-full text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="radio-1" checked>
<label for="radio-1" class="text-sm text-gray-500 ms-2 dark:text-neutral-400">Default</label>
</div>
<div class="flex">
<input type="radio" name="radio-group" class="shrink-0 mt-0.5 border-gray-200 rounded-full text-blue-600 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800" id="radio-2">
<label for="radio-2" class="text-sm text-gray-500 ms-2 dark:text-neutral-400">Checked</label>
</div>
</div>Toggle switch
<input type="checkbox" id="hs-basic-usage" class="relative w-[3.25rem] h-7 p-px bg-gray-100 border-transparent text-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:ring-blue-600 disabled:opacity-50 disabled:pointer-events-none checked:bg-none checked:text-blue-600 checked:border-blue-600 focus:checked:border-blue-600 dark:bg-neutral-800 dark:border-neutral-700 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-600
before:inline-block before:size-6 before:bg-white checked:before:bg-white before:translate-x-0 checked:before:translate-x-full before:rounded-full before:shadow before:transform before:ring-0 before:transition before:ease-in-out before:duration-200 dark:before:bg-neutral-400 dark:checked:before:bg-white">
<label for="hs-basic-usage" class="text-sm text-gray-500 ms-3 dark:text-neutral-400">Toggle switch</label>Components - Buttons
<!-- Solid Buttons -->
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none">
Primary
</button>
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-gray-500 text-white hover:bg-gray-600 focus:outline-none focus:bg-gray-600 disabled:opacity-50 disabled:pointer-events-none">
Secondary
</button>
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-teal-500 text-white hover:bg-teal-600 focus:outline-none focus:bg-teal-600 disabled:opacity-50 disabled:pointer-events-none">
Success
</button>
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-red-500 text-white hover:bg-red-600 focus:outline-none focus:bg-red-600 disabled:opacity-50 disabled:pointer-events-none">
Danger
</button>
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-yellow-500 text-white hover:bg-yellow-600 focus:outline-none focus:bg-yellow-600 disabled:opacity-50 disabled:pointer-events-none">
Warning
</button>
<!-- Outline Buttons -->
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-blue-600 text-blue-600 hover:border-blue-500 hover:text-blue-500 focus:outline-none focus:border-blue-500 focus:text-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:border-blue-500 dark:text-blue-500 dark:hover:text-blue-400 dark:hover:border-blue-400">
Outline Primary
</button>
<!-- Ghost Buttons -->
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent text-blue-600 hover:bg-blue-100 hover:text-blue-800 focus:outline-none focus:bg-blue-100 focus:text-blue-800 disabled:opacity-50 disabled:pointer-events-none dark:text-blue-500 dark:hover:bg-blue-800/30 dark:hover:text-blue-400 dark:focus:bg-blue-800/30 dark:focus:text-blue-400">
Ghost
</button>
<!-- Soft Buttons -->
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-100 text-blue-800 hover:bg-blue-200 focus:outline-none focus:bg-blue-200 disabled:opacity-50 disabled:pointer-events-none dark:text-blue-400 dark:bg-blue-800/30 dark:hover:bg-blue-800/20 dark:focus:bg-blue-800/20">
Soft
</button>
<!-- White Button -->
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-sm hover:bg-gray-50 focus:outline-none focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700">
White
</button>
<!-- Button with icon -->
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none">
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
Add item
</button>
<!-- Button sizes -->
<button type="button" class="py-2 px-3 inline-flex items-center gap-x-2 text-xs font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700">
Small
</button>
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700">
Default
</button>
<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-base font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700">
Large
</button>Components - Alerts
<!-- Info Alert -->
<div class="bg-blue-100 border border-blue-200 text-sm text-blue-800 rounded-lg p-4 dark:bg-blue-800/10 dark:border-blue-900 dark:text-blue-500" role="alert" tabindex="-1" aria-labelledby="hs-with-description-label">
<div class="flex">
<div class="shrink-0">
<svg class="shrink-0 size-4 mt-0.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 16v-4"></path>
<path d="M12 8h.01"></path>
</svg>
</div>
<div class="ms-4">
<h3 id="hs-with-description-label" class="text-sm font-semibold">
Info
</h3>
<div class="mt-1 text-sm text-blue-700 dark:text-blue-400">
A simple info alert with an example link.
</div>
</div>
</div>
</div>
<!-- Success Alert -->
<div class="bg-teal-100 border border-teal-200 text-sm text-teal-800 rounded-lg p-4 dark:bg-teal-800/10 dark:border-teal-900 dark:text-teal-500" role="alert" tabindex="-1">
<span class="font-bold">Success</span> Your purchase was successful.
</div>
<!-- Warning Alert -->
<div class="bg-yellow-100 border border-yellow-200 text-sm text-yellow-800 rounded-lg p-4 dark:bg-yellow-800/10 dark:border-yellow-900 dark:text-yellow-500" role="alert" tabindex="-1">
<span class="font-bold">Warning</span> Please check your input.
</div>
<!-- Error Alert -->
<div class="bg-red-100 border border-red-200 text-sm text-red-800 rounded-lg p-4 dark:bg-red-800/10 dark:border-red-900 dark:text-red-500" role="alert" tabindex="-1">
<span class="font-bold">Error</span> Something went wrong.
</div>
<!-- Dismissible Alert -->
<div id="dismiss-alert" class="hs-removing:translate-x-5 hs-removing:opacity-0 transition duration-300 bg-teal-50 border border-teal-200 text-sm text-teal-800 rounded-lg p-4 dark:bg-teal-800/10 dark:border-teal-900 dark:text-teal-500" role="alert" tabindex="-1">
<div class="flex">
<div class="shrink-0">
<svg class="shrink-0 size-4 mt-0.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path>
<path d="m9 12 2 2 4-4"></path>
</svg>
</div>
<div class="ms-2">
<div class="text-sm font-medium">
Successfully uploaded.
</div>
</div>
<div class="ps-3 ms-auto">
<div class="-mx-1.5 -my-1.5">
<button type="button" class="inline-flex bg-teal-50 rounded-lg p-1.5 text-teal-500 hover:bg-teal-100 focus:outline-none focus:bg-teal-100 dark:bg-transparent dark:hover:bg-teal-800/50 dark:focus:bg-teal-800/50" data-hs-remove-element="#dismiss-alert">
<span class="sr-only">Dismiss</span>
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
</div>
</div>
</div>
</div>Dark mode
Preline UI supports dark mode through Tailwind's dark variant. To toggle it manually instead of following the operating system setting, override that variant with your own selector:
/* main.css */
@import 'tailwindcss';
@custom-variant dark (&:where(.dark, .dark *));<!-- Dark mode toggle -->
<button type="button" class="hs-dark-mode-active:hidden block hs-dark-mode group flex items-center text-gray-600 hover:text-blue-600 font-medium dark:text-neutral-400 dark:hover:text-neutral-500" data-hs-theme-click-value="dark">
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path>
</svg>
</button>
<button type="button" class="hs-dark-mode-active:block hidden hs-dark-mode group flex items-center text-gray-600 hover:text-blue-600 font-medium dark:text-neutral-400 dark:hover:text-neutral-500" data-hs-theme-click-value="light">
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2"></path>
<path d="M12 20v2"></path>
<path d="m4.93 4.93 1.41 1.41"></path>
<path d="m17.66 17.66 1.41 1.41"></path>
<path d="M2 12h2"></path>
<path d="M20 12h2"></path>
<path d="m6.34 17.66-1.41 1.41"></path>
<path d="m19.07 4.93-1.41 1.41"></path>
</svg>
</button>React/Next.js integration
// components/Modal.tsx
'use client'
import { useEffect } from 'react'
interface ModalProps {
id: string
title: string
children: React.ReactNode
isOpen: boolean
onClose: () => void
}
export function Modal({ id, title, children, isOpen, onClose }: ModalProps) {
useEffect(() => {
// Reinitialize Preline after mounting
import('preline').then((module) => {
if (typeof window !== 'undefined' && (window as any).HSOverlay) {
(window as any).HSOverlay.autoInit()
}
})
}, [])
return (
<div
id={id}
className={`hs-overlay ${isOpen ? 'open' : 'hidden'} size-full fixed top-0 start-0 z-[80] overflow-x-hidden overflow-y-auto pointer-events-none`}
role="dialog"
tabIndex={-1}
aria-labelledby={`${id}-label`}
>
<div className="hs-overlay-open:mt-7 hs-overlay-open:opacity-100 hs-overlay-open:duration-500 mt-0 opacity-0 ease-out transition-all sm:max-w-lg sm:w-full m-3 sm:mx-auto">
<div className="flex flex-col bg-white border shadow-sm rounded-xl pointer-events-auto dark:bg-neutral-800 dark:border-neutral-700 dark:shadow-neutral-700/70">
<div className="flex justify-between items-center py-3 px-4 border-b dark:border-neutral-700">
<h3 id={`${id}-label`} className="font-bold text-gray-800 dark:text-white">
{title}
</h3>
<button
type="button"
className="size-8 inline-flex justify-center items-center gap-x-2 rounded-full border border-transparent bg-gray-100 text-gray-800 hover:bg-gray-200 focus:outline-none focus:bg-gray-200 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:text-neutral-400 dark:focus:bg-neutral-600"
aria-label="Close"
onClick={onClose}
>
<span className="sr-only">Close</span>
<svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
</div>
<div className="p-4 overflow-y-auto">
{children}
</div>
</div>
</div>
</div>
)
}Pricing
Open Source (free)
- 640+ components
- 204 blocks and 5 premium templates
- Dual licence: MIT or fair use
- Full documentation
- Dark mode
- Responsive
Preline Pro (from $249)
Payment is one time and access is perpetual, and there are three variants: Pro at $249 for a single developer, Team at $459 for up to fifteen people, and Enterprise on request, the only one that permits redistribution. All of them carry the same contents:
- 780+ blocks
- 21 premium templates, 207 pages in total
- Admin dashboards, landing pages, and ecommerce demos
- General support covering account, licence, and product, with no technical support
- No Figma files; the Figma design system is published separately and free
FAQ - frequently asked questions
Does Preline UI require JavaScript?
For static components (buttons, alerts, cards) - no. For interactive ones (accordion, tabs, modal, dropdown) - yes.
Does Preline UI work with Next.js App Router?
Yes! You need to import Preline in a useEffect in your main layout/components.
How do I add custom styles to components?
You can override Tailwind classes directly in the HTML - Preline uses standard Tailwind classes.
What is the difference between Preline UI and Flowbite?
Preline ships over 640 components free, while Flowbite has roughly 68 component types and richer framework wrappers. The two numbers count different things, so treat them as a hint rather than a measure: compare the list of elements you actually need.
Can I use Preline commercially?
Yes. The licensing is dual, and the MIT option allows commercial use at no cost.
Preline UI against the alternatives
| Feature | Preline UI | Flowbite | daisyUI | shadcn/ui |
|---|---|---|---|---|
| How behaviour is driven | attributes in markup | attributes or components | CSS classes only | components in your code |
| Framework dependency | none | none, wrappers available | none | React |
| JavaScript layer | its own | its own | none | a behaviour library |
| Licence | dual, MIT or fair use | MIT | MIT | MIT |
| Component updates | with the package | with the package | with the package | manual, the code is yours |
The choice between the first two is the hardest, since they resemble each other most: both lean on attribute driven behaviour, both have free and paid tiers, both work outside frameworks. Decide on which set carries the components you actually need and which look suits you, because technically the differences are small.
If your project runs on React and there is no reason to avoid a dependency, the copied component code approach is worth considering. You gain full control and typing, and lose automatic updates.
One more category sits outside the table and is easy to confuse with the entries above, because the numbers look alike. Block sets such as TailGrids give you neither a button nor a dropdown but a whole page section: a header with a title, a description, and an image, ready to paste. The code is copied into your project, so updates do not arrive, and there is no toggle logic or keyboard handling there, nor is any meant to be. That is a layer above components rather than instead of them, and on a landing page it saves a different kind of work than a set like this one.
Common mistakes
The first is skipping reinitialisation after a view change, described above. It is the most frequent cause of reports saying the component does not work, and the fix is one line.
The second is not letting Tailwind scan the library's files. The classes the components use have to be found by the scanner, so the package directory must appear in configuration. The symptom is an unstyled interface that looks like a broken installation.
The third is assuming accessibility is handled. The components carry role attributes, but keyboard handling varies in depth. For elements central to your interface it pays to tab through them and check focus behaves correctly rather than taking it on trust.
On a project where accessibility is a formal requirement rather than good practice, a better starting point is often a behaviour layer written with that in mind, Headless UI for instance, with the appearance assembled yourself on Tailwind classes. That costs more work upfront and pays for itself at audit time.
The fourth is mixing this library with another behaviour layer in one project. Two systems attaching handlers to the same elements can collide, particularly around modal dialogs and focus trapping.
The fifth is copying markup without stripping what you do not use. Documentation examples are written broadly, with variants and classes covering different situations, so pasting leaves a fair amount of dead code that stays in the project forever unless reviewed immediately.
The sixth, visible only after a few months, is the absence of your own intermediate layer. Since components are markup rather than code, thirty buttons in a project are thirty independent copies of the same classes. Changing a button's appearance then means thirty edits instead of one. The remedy is extracting your own components wrapping that markup right at the start, even when it seems unnecessary at the time, because the cost of doing it later grows every week.
The source and full component list live in the Preline repository, and documentation on the project site.