Every time we build a UI with raw Tailwind, we run into common frontend issues, such as fewer reusable components, consistency breaking down at scale, and repeated overrides. Flexibility is valuable, but without structure, it can quickly become maintenance overhead.
That’s what pushed us to build Hummingbird. It’s a Tailwind component system designed to be a complete solution that brings structure to Tailwind's utility model without taking away the control developers actually need. The goal is not to replace Tailwind’s utility power, but to structure it so the components remain consistent without sacrificing developer control.
Find Us on Product Hunt
A raw Tailwind button looks like this:
<button class="inline-flex items-center justify-center px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200">
Click me
</button>
Now, think of applying a "danger" variant consistently across 12 components, or supporting dark mode without doubling the class count, or handing this codebase to a new developer. You end up with either a CSS extraction layer, which defeats Tailwind's purpose, or inconsistent one-off implementations.
Hummingbird is our answer to that. It provides predefined components, extensible styling as needed, and an API that keeps customization predictable. The goal is to combine Tailwind’s flexibility with a structured component system that scales without introducing unnecessary complexity.
Here's how we approached it.
Component Classes on Top of Utilities
The first decision we had to make was where to sit in the stack. Too low, and we'd just be documenting Tailwind patterns. Too high, and we'd be building another opinionated component library that locks you in when you need to go your own way.
So, we landed on a middle ground — semantic component classes built from Tailwind’s utilities internally, but used as simple, stable class names.
<button class="btn btn-primary">Click me</button>
Here, .btn and .btn-primary encapsulate the right utility composition for that context. But because Tailwind utilities still apply on top, you're never blocked when you need to customize further:
<button class="btn btn-primary rounded-full px-8">Pill button</button>
The variant system handles the common case. Utilities handle everything else.
Reduced Code Writing
One thing we didn't expect was how much this decision improved code composability. Compare writing and maintaining components in raw utility classes versus in Hummingbird's component classes. The difference shows up immediately in how easy it is to scan, edit, and extend code.
Semantic class names make intent obvious. You're not reading 20 utilities to figure out what an element is supposed to be. You write less code, and what you write is easier to reason about.
Easy Theming
Most theming systems grow bloated over time - too many design tokens, too many override layers, and unclear ownership of visual decisions. That increases cognitive load, slows onboarding, and makes small brand updates unnecessarily expensive.
We wanted to avoid that. Built on Tailwind CSS v4, Hummingbird uses a minimal semantic-token approach, reducing override noise and maintenance costs. It provides more control over the entire theming system with less effort, ensuring a seamless, faster development experience.
For example, you can use the theme variables like --color-primary and --shadow-md via standard Tailwind utilities like bg-primary, text-primary, and shadow-md in your markup. No additional configuration or separate theming layer required.
Hummingbird also allows straightforward theme overrides. You can override any styles globally using a theme variable by redefining them in an @theme { ... } block after importing the CSS.
/* Global */
@theme {
/* Background colors */
--background-color-subtle: var(--color-gray-50);
--background-color-muted: var(--color-gray-100);
--background-color-default: var(--color-white);
--background-color-highlight: var(--color-gray-200);
--background-color-emphasis: var(--color-gray-300);
}
/* Component-specific */
.btn {
--btn-bg: var(--background-color-highlight);
--btn-color: var(--text-color-default);
--btn-hover-bg: var(--color-hover);
--btn-disabled-bg: var(--color-disabled);
--btn-disabled-color: var(--color-disabled-color);
}
For multiple themes, such as light, dark, or any custom themes, scope your overrides with @variant <ThemeName> {}.
/*for multiple theming*/
@theme {
--color-primary: var(--color-blue-500);
--color-secondary: var(--color-purple-500);
}
@variant dark {
--color-primary: var(--color-blue-400);
--color-secondary: var(--color-purple-400);
}
@variant forest {
--color-primary: var(--color-blue-600);
--color-secondary: var(--color-purple-600);
}
Color scheme changes are now just variable swaps. Component-level overrides don't affect anything outside that component.
Interactive Components & JavaScript
In most setups, adding interactive components requires writing separate scripts for each interaction. The more components you add, the more scripts you write, and it becomes repetitive fast. To address this, we built Hummingbird’s logic on a proven foundation instead of starting from scratch.
Hummingbird adapts Bootstrap’s JavaScript core, a stable, reliable system for UI behavior. It uses a Data API approach with built-in TypeScript support, allowing you to add interactivity by simply adding data attributes to your HTML. No additional script is needed to initialize a dropdown or trigger an offcanvas. The behavior is declarative and predictable.
<div class="dropdown">
<button class="btn btn-subtle-neutral" type="button" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Profile</a></li>
<li><a class="dropdown-item" href="#">My Account</a></li>
<li><a class="dropdown-item" href="#">Dashboard</a></li>
</ul>
</div>
JavaScript API
If you need more control, such as manually customizing behavior, Hummingbird lets you do so with component-specific modules.
For example, if you’re designing a Modal component:
import Modal from "@hummingbirdui/hummingbird";
const openBtn = document.querySelector("[data-open-demo-modal]");
const myModal = new Modal(".modal");
openBtn.addEventListener("click", () => {
myModal.show();
});
We also made a deliberate decision to keep the JavaScript API minimal. No complex plugin system to learn, no configuration overhead. Just use the methods you actually need:
const myCarouselElement = document.querySelector('#myCarousel')
const carousel = new hummingbird.Carousel(myCarouselElement, {
interval: 2000,
touch: false
})
This initializes a carousel component, sets it to auto-advance every 2 seconds, and disables touch control for each component.
Bundle Strategy: Keep It Clean
Hummingbird supports both ESM and CJS builds. ESM is used by modern bundlers like Vite, Rollup, and Webpack 5+. CJS is used in Node.js or older tooling that relies on require(). The bundler automatically selects the right one based on your project’s configuration. So, no manual configuration is required.
Components are individually importable, so unused ones don't end up in the bundle, keeping it optimized. Pick the specific JavaScript plugin you need for your design.
import { Modal, Dropdown } from "@hummingbirdui/hummingbird";
Getting Started in One Command
We wanted the setup experience to make as easy as building with it. Hummingbird offers a quicker setup using flags, such as --yes, -- ts, -t, etc. Getting from zero to a running project looks like this:
npx create-hummingbird-app@latest my-app --yes
cd my-app
npm run dev
Here, the --yes skips all prompts and sets up Vite + Vanilla JS with Tailwind and Hummingbird pre-configured.
If you require TypeScript support instead of vanilla JS, you run the following:
npx create-hummingbird-app@latest -t vite --ts
This installs the default TypeScript template (Vite-ts).
For Manual installation, you can follow the steps below:
Step1: Install Tailwind CSS
Ensure the project is set up with Tailwind CSS.
- Install Tailwind CSS Packages as a Vite plugin via npm
npm install tailwindcss @tailwindcss/vite
Add the Vite plugin @tailwindcss/vite to your vite.config.ts file
Import Tailwind CSS through your main CSS file
@import "tailwindcss";
Step2: Install Hummingbird
Install Hummingbird via a preferred package manager:
npm install @hummingbirdui/hummingbird
Step3: Import CSS
Import Hummingbird styles in the main CSS file (e.g., styles.css).
@import "tailwindcss";
@import "@hummingbirdui/hummingbird";
Step4: Initialize JS plugins
Include Hummingbird JavaScript at the end of the HTML body.
<script src="../path/to/@hummingbirdui/hummingbird/dist/hummingbird.bundle.min.js"></script>
Alternatively, if using a build system (like Vite or Webpack), import Hummingbird directly into the JavaScript entry file.
import '@hummingbirdui/hummingbird';
Framework Integration
The component class approach is framework-agnostic at the CSS level. In React:
<button className="btn btn-primary">Click me</button>
In Vue:
<button class="btn btn-primary">Click me</button>
Same classes, different frameworks, no additional configuration required. For JavaScript-dependent components, initialize them in the appropriate lifecycle hook, such as useEffect in React, onMounted in Vue. The Framework Guides cover the specifics.
Accessibility
Unlike CSS-only component libraries, where ARIA attributes have to be wired up manually, Hummingbird's interactive components include them out of the box. It allows developers to build without requiring them to start from scratch or rely on framework-specific solutions.
Components like modals, dropdowns, and tooltips work across touch, mouse, and keyboard inputs and are readable by screen readers. In some cases, developers may need to layer on additional ARIA behaviors depending on the specific interaction being built, but the baseline is covered.
On color contrast, the default palette is designed to meet WCAG's recommended ratios, 4.5:1 for text and 3:1 for non-text elements. Custom themes still need to be verified independently. The .sr-only class visually hides elements while keeping them accessible to screen readers. This is useful for labels, descriptions, or context that only assistive technology users need.
RTL Support
Hummingbird components automatically adapt to RTL layouts when dir="rtl" is set on the root element:
html:
<html dir="rtl">
...
</html>
For directional spacing and alignment, logical classes are the right approach. Use ms-* and me-* for margins, ps-* and pe-* for padding, and text-start/text-end for alignment. Avoid left-* and right-* - these are direction-specific and break in RTL. Use start-* and end-* instead.
When building interfaces that need to support both LTR and RTL, the ltr: and rtl: variants let you apply styles conditionally:
html:
<div class="ltr:ml-3 rtl:mr-3">
...
</div>
For projects building RTL layouts, the same convention applies to your own markup. Avoid left-* and right-*; use start-* and end-* so the layout responds correctly when the direction changes.
So, Why Hummingbird as A Tailwind Component System?
Our goal is to build one of the most sensible Tailwind component systems that removes the trade-offs between development speed and time. A feature-rich UI library that creates balance with design consistency, empowering developers to create high-quality, scalable applications.
Not so low that you're managing 30 utility classes per element. Not so high that customization requires working around the library. We aim to deliver a Tailwind component system built on a component-class layer, configured via CSS variables, and extensible with utilities when you need to go further.
If you want consistent components, runtime theming, interactive behavior, and the flexibility to reach for raw utilities when you need to, this architecture makes all of that possible at once. Just open your terminal and type:
npx create-hummingbird-app@latest my-app --yes
And get started, the documentation covers the rest.

Top comments (0)