Mode
The classic path — full CSS library, add njx.js when you need it
Getting started

Quick Start

Connect the library to your project in under a minute — no build tools required.

RECOMMENDED
Full CSS + JS
The classic path — 30+ component classes, utilities and 9 themes; add njx.js when you need interactivity.
style.min.css · 308 KB
🚀
Astro
npm install + one import in your Layout. Zero-JS by default — made for content sites and landings.
import 'njx-ui/css/style.min.css'
JS
Interactive · JS / Alpine
Everything in Full plus live modals, tabs, toasts, dropdowns — driven by Alpine.js or vanilla njx.js.
+ Alpine.js / njx.js
1
Add the stylesheet
One <link> tag in <head>. Nothing else required.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/njx-ui@1/css/style.min.css">
2
Set a theme
Add data-theme to the <html> element. All components adapt automatically.
<html lang="en" data-theme="dark">
<!-- dark · light · red · blue · green · cyan · yellow · pink · purple -->
3
Use component classes
Add class names to HTML elements. Most components are pure CSS — no JavaScript needed.
index.html
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/njx-ui@1/css/style.min.css">
</head>
<body>
  <button class="btn btn-primary">Hello njX</button>
  <div class="card-glow">
    <h2 class="text-gradient-primary">Works.</h2>
  </div>
</body>
</html>
4
Add JavaScript (optional)
Interactive components — collapse, tabs, dropdown, modal, carousel, toast — need one script.
<!-- Adds: collapseToggle, tabSwitch, dropdownToggle,
     openModal, closeModal, showToast, initCarousels -->
<script src="https://cdn.jsdelivr.net/npm/njx-ui@1/js/njx.js"></script>
Native details/summary components work without JS. See the JavaScript reference for all functions.
📦
More install options: npm install, download ZIP, or import individual CSS files. See the Quick Start guide for all methods.
Theme system

Themes

9 built-in themes, each defined as a complete set of CSS custom properties. Switch the entire UI with one attribute — no JavaScript required.

Dark
Light
Red
Blue
Green
Cyan
Yellow
Pink
Purple
/* Via HTML attribute */
<html data-theme="purple">

/* Via JavaScript */
document.documentElement.setAttribute('data-theme', 'purple');

/* With localStorage persistence */
function setTheme(name) {
  document.documentElement.setAttribute('data-theme', name);
  localStorage.setItem('theme', name);
}
const saved = localStorage.getItem('theme');
if (saved) setTheme(saved);
/* njx-alpine.js preset — load it before Alpine, zero helpers to write */
<script defer src="https://cdn.jsdelivr.net/npm/njx-ui@1/js/njx-alpine.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>

/* Toggle dark / light */
<button x-data="njxTheme" @click="toggle()">Toggle theme</button>

/* Set a specific theme */
<div x-data="njxTheme">
  <button @click="set('purple')" :class="{ active: t === 'purple' }">Purple</button>
  <button @click="set('cyan')"   :class="{ active: t === 'cyan' }">Cyan</button>
</div>

/* data-theme on <html> + localStorage persistence — handled by the preset */
Reference

Components

30+ components included. Browse live demos and code at the components page.

🔲 Buttons
.btn
.btn-primary · .btn-outline
.btn-ghost · .btn-gradient
.btn-glow · .btn-shine
.btn-sm · .btn-lg
🃏 Cards
.card · .card-glass
.card-dark · .card-bordered
.card-glow · .box
📋 Forms
.inp · .inp-primary
.inp-glass · .float-field
.toggle · .check
.inp-select
🗂 Tabs JS
.tab-wrap · .tab-nav
.tab-btn · .tab-panel
.tab-nav-pills
.tab-nav-boxed · .tab-nav-card
.tab-nav.is-full · .tab-badge
🪗 Collapse JS / native
details.collapse (native)
.collapse + JS toggle
.collapse-plus · .collapse-ghost
.collapse-primary
.accordion · .accordion-flush
💬 Modals JS
.lib-modal-overlay
.lib-modal-box
.is-success · .is-error
.is-warning · .is-info
.lib-modal-close
🧭 Navigation
.nav · .nav-brand · .nav-link
.nav-solid · .nav-glass
.nav-primary · .nav-dark
.nav-floating · .nav-underline
.nav-sm · .nav-lg
📂 Dropdown JS / native
details.dropdown (native)
.dropdown + JS toggle
.dropdown-menu · .dropdown-item
.dropdown-right · .dropdown-top
.dropdown-primary · .dropdown-dark
🎠 Carousel CSS + JS dots
.carousel · .carousel-item
.carousel-center · .carousel-gap
.carousel-vertical
.carousel-btn · .carousel-dots
.carousel-item-sm/md/lg/xl
🔗 Links
.link · .link-underline
.link-slide · .link-arrow
.link-external · .link-muted
.link-btn · .link-card
.link-list
🏷 Tags & Badges
.tag · .badge
.tag-primary · .tag-success
.tag-accent · .tag-outline
📊 Table & Progress
.table
.progress · .progress-bar
🔔 Notifications JS
showToast(msg, type)
.lib-toast · .lib-toast-success
.lib-toast-error · .lib-toast-primary
🌈 Gradients
.gradient-primary
.text-gradient-primary
.gradient-blue-purple
✨ Hover FX
.hover-lift · .hover-glow
.hover-brighten · .hover-rotate
Components marked JS require njx.js for interactivity. Components marked native use <details>/<summary> and work without any JavaScript.
Reference

JavaScript — njx.js

One script file for all interactive components. Include it once — everything initializes automatically.

Connection

index.html
<!-- One file, before </body> — everything initializes automatically -->
<script src="https://cdn.jsdelivr.net/npm/njx-ui@1/js/njx.js"></script>
</body>
</html>

Which components need JS

Component Without JS With njx.js
Collapse details.collapse — работает нативно collapseToggle() для div-based + аккордеон
Dropdown details.dropdown — работает нативно dropdownToggle() для div-based + close on outside click
Tabs tabSwitch() — переключение панелей
Modal openModal() / closeModal() + Escape
Carousel Scroll-snap работает, якорные кнопки работают initCarousels() — синхронизация dots при свайпе
Toast showToast() — динамические уведомления
Buttons, Cards, Forms, Nav, Links, Tags, Table, Progress, Gradients, Animations ✓ Чистый CSS — JS не нужен

Function reference

collapseToggle(header) collapse

Toggles .is-open on the closest .collapse element.

<div class="collapse">
  <div class="collapse-header" onclick="collapseToggle(this)">
    Title <span class="collapse-icon"></span>
  </div>
  <div class="collapse-body">Content</div>
</div>
accordionToggle(header) accordion

Like collapseToggle but closes all sibling .collapse items in the same .accordion wrapper first.

<div class="accordion">
  <div class="collapse">
    <div class="collapse-header" onclick="accordionToggle(this)">Section 1</div>
    <div class="collapse-body">...</div>
  </div>
  <div class="collapse">
    <div class="collapse-header" onclick="accordionToggle(this)">Section 2</div>
    <div class="collapse-body">...</div>
  </div>
</div>
dropdownToggle(el) dropdown

Toggles .is-open on a .dropdown element. All other open dropdowns are closed first. Outside-click listener is added automatically.

<div class="dropdown">
  <button class="btn" onclick="dropdownToggle(this.parentElement)">Menu</button>
  <div class="dropdown-menu">
    <a class="dropdown-item">Item</a>
  </div>
</div>
tabSwitch(btn) tabs

Activates the clicked .tab-btn and shows the corresponding .tab-panel by index inside .tab-wrap.

<div class="tab-wrap">
  <div class="tab-nav tab-nav-pills">
    <button class="tab-btn is-active" onclick="tabSwitch(this)">Tab 1</button>
    <button class="tab-btn" onclick="tabSwitch(this)">Tab 2</button>
  </div>
  <div class="tab-content">
    <div class="tab-panel is-active">Content 1</div>
    <div class="tab-panel">Content 2</div>
  </div>
</div>
openModal(id) / closeModal(el) modal

Opens a modal by its ID / closes by element reference. Escape key auto-closes all open modals. Click on overlay background also closes.

<!-- Trigger -->
<button onclick="openModal('my-modal')">Open</button>

<!-- Modal -->
<div id="my-modal" class="lib-modal-overlay"
     onclick="if(event.target===this)closeModal(this)">
  <div class="lib-modal-box">
    <button class="lib-modal-close"
      onclick="closeModal(document.getElementById('my-modal'))"></button>
    <!-- .is-success .is-error .is-warning .is-info -->
  </div>
</div>
showToast(msg, type, duration) toast

Shows a toast notification. Types: success · error · primary · dark. Duration in ms (default 2200). Requires <div id="lib-toast-container"></div> in the page.

<!-- Required container (place anywhere in body) -->
<div id="lib-toast-container"></div>

// Usage
showToast('Saved!', 'success');
showToast('Something went wrong', 'error', 3000);
showToast('Info message', 'primary');
initCarousels() / carouselGo(el, idx) carousel

initCarousels() is called automatically on DOMContentLoaded — it attaches a scroll listener to every .carousel-wrap and syncs .carousel-dot.is-active. carouselGo(el, idx) programmatically scrolls to a slide by 0-based index.

// Scroll to slide index 2 programmatically
const car = document.querySelector('.carousel');
carouselGo(car, 2);

// Re-init after dynamic content (e.g. AJAX)
initCarousels();
💡
Native alternative for collapse & dropdown: Use <details class="collapse"> and <details class="dropdown"> — the browser handles open/close natively, zero JS needed.

Alpine.js — njx.js is optional

Every interactive component works by toggling a single state class (.is-open, .is-active, .open). njx.js is one way to toggle it — Alpine.js is another: keep the same markup and drive the class from x-data. Ready-made recipes live on the components page — look for the Alpine.js tab in the code windows of Collapse, Tabs, Dropdown, Modal, Toast and Carousel.

<!-- Alpine.js from CDN — njx.js becomes optional -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>

njx-alpine.js — presets, one word per component

The raw recipes below show how it works. When you just want it working, ship js/njx-alpine.js (~3 KB) — it registers Alpine.data() presets for every interactive component: njxCollapse, njxAccordion, njxTabs, njxDropdown, njxModal, njxToasts, njxCarousel, njxTheme. Presets toggle the same classes njx.js uses, handle outside-click / Escape / body-scroll for you, and read slide counts from the markup.

<!-- njx-alpine.js: Alpine.data() presets — one word instead of a hand-written object.
     Load it BEFORE Alpine (both defer — document order decides). -->
<script defer src="https://cdn.jsdelivr.net/npm/njx-ui@1/js/njx-alpine.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>

<!-- before: x-data="{ open: false }" + :class bindings
     after:  one preset name, classes are synced for you -->
<div class="collapse" x-data="njxCollapse">
  <button class="collapse-header" @click="toggle()">Question</button>
  <div class="collapse-body">Answer</div>
</div>

<div class="tab-wrap" x-data="njxTabs">
  <div class="tab-nav">
    <button class="tab-btn" :class="{ 'is-active': isActive(0) }" @click="select(0)">One</button>
    <button class="tab-btn" :class="{ 'is-active': isActive(1) }" @click="select(1)">Two</button>
  </div>
</div>

<div class="js-carousel-wrap" x-data="njxCarousel(0, 4000)"> <!-- auto count · autoplay 4s -->
  <div class="js-carousel">
    <div class="js-carousel-track" :style="offset">…slides…</div>
  </div>
  <button class="js-carousel-btn js-prev" @click="prev()">&lsaquo;</button>
  <button class="js-carousel-btn js-next" @click="next()">&rsaquo;</button>
</div>

<button x-data="njxTheme" @click="toggle()" x-text="t === 'dark' ? '☀️' : '🌙'"></button>

<!-- All presets: njxCollapse · njxAccordion · njxTabs · njxDropdown
     njxModal · njxToasts · njxCarousel · njxTheme -->

Alpine recipes

x-data="{ open: false }" collapse · accordion

Alpine toggles the same .is-open class that collapseToggle() would. The accordion variant keeps one item open via a shared active index.

<!-- Same markup, same .is-open class — Alpine just toggles it -->
<div class="collapse" x-data="{ open: false }" :class="{ 'is-open': open }">
  <div class="collapse-header" @click="open = !open">
    Title <span class="collapse-icon"></span>
  </div>
  <div class="collapse-body">Content</div>
</div>

<!-- Exclusive accordion — one item open at a time -->
<div class="accordion" x-data="{ active: null }">
  <div class="collapse" :class="{ 'is-open': active === 1 }">
    <div class="collapse-header" @click="active = active === 1 ? null : 1">
      Section A <span class="collapse-icon"></span>
    </div>
    <div class="collapse-body">Content A</div>
  </div>
</div>
x-data="{ active: null }" accordion

Exclusive open — the shared active index closes the previous item automatically. Same .is-open class on .collapse.

<!-- Exclusive accordion: one shared index instead of collapseToggle() -->
<div class="accordion" x-data="{ active: null }">
  <div class="collapse" :class="{ 'is-open': active === 0 }">
    <button class="collapse-header" @click="active = active === 0 ? null : 0">
      First question <span class="collapse-icon"></span>
    </button>
    <div class="collapse-body">Answer one.</div>
  </div>
  <div class="collapse" :class="{ 'is-open': active === 1 }">
    <button class="collapse-header" @click="active = active === 1 ? null : 1">
      Second question <span class="collapse-icon"></span>
    </button>
    <div class="collapse-body">Answer two.</div>
  </div>
</div>

<!-- Or the njx-alpine.js preset: x-data="njxAccordion" → toggle(i) / isOpen(i) -->
x-data="{ tab: 0 }" tabs

One number of state. Buttons and panels share the same .is-active binding — no tabSwitch() needed.

<div class="tab-wrap" x-data="{ tab: 0 }">
  <div class="tab-nav">
    <button class="tab-btn" :class="{ 'is-active': tab === 0 }" @click="tab = 0">Tab 1</button>
    <button class="tab-btn" :class="{ 'is-active': tab === 1 }" @click="tab = 1">Tab 2</button>
  </div>
  <div class="tab-content">
    <div class="tab-panel" :class="{ 'is-active': tab === 0 }">Content 1</div>
    <div class="tab-panel" :class="{ 'is-active': tab === 1 }">Content 2</div>
  </div>
</div>
@click.outside dropdown

@click.outside replaces the global document listener that njx.js registers — the menu closes itself.

<div class="dropdown" x-data="{ open: false }"
     :class="{ 'is-open': open }" @click.outside="open = false">
  <button class="btn btn-ghost btn-sm" @click="open = !open">
    Menu <span class="dropdown-arrow"></span>
  </button>
  <div class="dropdown-menu">
    <a href="#" class="dropdown-item" @click="open = false">Item</a>
  </div>
</div>
@keydown.escape.window modal

The same .open class on the overlay — @click.self closes on the backdrop, Escape closes from anywhere. No openModal/closeModal.

<div x-data="{ open: false }" @keydown.escape.window="open = false">
  <button class="btn btn-primary" @click="open = true">Open</button>

  <div class="lib-modal-overlay" :class="{ open }" @click.self="open = false">
    <div class="lib-modal-box is-primary">
      <button class="lib-modal-close" @click="open = false"></button>
      <div class="lib-modal-title">Title here</div>
      <div class="lib-modal-actions">
        <button class="btn btn-primary btn-sm" @click="open = false">Confirm</button>
        <button class="btn btn-ghost btn-sm" @click="open = false">Cancel</button>
      </div>
    </div>
  </div>
</div>
template x-for toast

A toast stack as plain state: fire() pushes, setTimeout auto-dismisses. Same .lib-toast classes as showToast().

<div x-data="{
  toasts: [], id: 0,
  fire(msg, type) {
    const t = { id: ++this.id, msg, type };
    this.toasts.push(t);
    setTimeout(() => this.dismiss(t.id), 2200);
  },
  dismiss(id) { this.toasts = this.toasts.filter(t => t.id !== id); }
}">
  <button class="btn btn-primary btn-sm" @click="fire('Saved!', 'success')">Success</button>
  <button class="btn btn-ghost btn-sm" @click="fire('Error occurred', 'error')">Error</button>

  <div id="lib-toast-container">
    <template x-for="t in toasts" :key="t.id">
      <div class="lib-toast" :class="'lib-toast-' + t.type"
           @click="dismiss(t.id)" x-text="t.msg"></div>
    </template>
  </div>
</div>
:style="translateX(-i * 100%)" carousel

The track offset is derived from one index — prev / next / dots all just change i. Works with .js-carousel-track, .hero-slider-track and .testi-track alike.

<!-- Track position is derived state — one :style binding -->
<div class="js-carousel-wrap" x-data="{ i: 0, n: 3 }">
  <div class="js-carousel">
    <div class="js-carousel-track"
         :style="'transform: translateX(-' + i * 100 + '%)'">
      <div>Slide 1</div><div>Slide 2</div><div>Slide 3</div>
    </div>
  </div>
  <button class="js-carousel-btn js-prev" @click="i = (i - 1 + n) % n"></button>
  <button class="js-carousel-btn js-next" @click="i = (i + 1) % n"></button>
  <div class="js-dots">
    <template x-for="d in n" :key="d">
      <button class="js-dot" :class="{ 'is-active': i === d - 1 }" @click="i = d - 1"></button>
    </template>
  </div>
</div>

<!-- Or the njx-alpine.js preset: x-data="njxCarousel(0, 4000)" — autoplay, hover pause, auto slide count -->
data-theme + localStorage theme toggle

Dark/light switch in one button — Alpine holds the state, data-theme does the styling, localStorage remembers the pick.

<!-- Dark/light toggle with localStorage — one line of state -->
<button class="btn btn-ghost btn-sm"
        x-data="{ t: document.documentElement.dataset.theme || 'dark' }"
        @click="t = t === 'dark' ? 'light' : 'dark';
                document.documentElement.dataset.theme = t;
                localStorage.setItem('theme', t)"
        x-text="t === 'dark' ? '☀ Light' : '🌙 Dark'"></button>
Live previews: every recipe above has a working demo on the components page — switch the code window to the Alpine.js tab.
Reference

Design Tokens

All values are CSS custom properties defined in _base.css. Use them in your own styles for automatic theme adaptation.

Colors
Token Description
--color-primary Main brand color (theme-reactive)
--color-accent Secondary accent
--color-success Green / success state
--color-error Red / error state
--color-warning Orange / warning state
--color-light Primary text
--color-muted Secondary / muted text
--сolor-dark Page background
--color-dark-secondary Card / surface background
Spacing (4px base)
Token Value
--space-14px
--space-28px
--space-312px
--space-416px
--space-624px
--space-832px
--space-1248px
--space-1664px
Typography
Token Value
--fs-xs12px
--fs-sm14px
--fs-base16px
--fs-lg18px
--fs-xl20px
--fs-2xl24px
--fs-3xl30px
--fs-4xl36px
--fs-5xl48px
Border Radius
--radius-none0
--radius-sm6px
--radius-md12px
--radius-lg20px
--radius-full9999px
Transitions
--ease-fast0.1s ease
--ease0.2s ease
--ease-slow0.4s ease
Shadows
--shadow-sm
--shadow-md
--shadow-lg
--shadow-primary
--shadow-accent
Reference

Using Design Tokens

How to write CSS that works with all 9 themes automatically.

9 themes automatically No hardcoded colors DRY — one token everywhere
1

Colors — --color-*

Never hardcode a hex value. Use a token — and when the theme changes, the color updates automatically everywhere.

❌ Bad — hardcoded
.btn {
  color: #14a0ff;
  border: 1px solid #14a0ff;
  background: rgba(20,160,255,0.1);
}
✅ Good — uses tokens
.btn {
  color: var(--color-primary);
  border: 1px solid var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
}
2

Spacing — --space-*

4px base grid. Index = multiplier × 4px.

--space-1
4px
--space-2
8px
--space-3
12px
--space-4
16px
--space-6
24px
--space-8
32px
--space-12
48px
--space-16
64px
.header { padding: var(--space-4) var(--space-6); }
.card   { padding: var(--space-6); margin-bottom: var(--space-4); }
.layout { gap: var(--space-4); }
3

Typography — --fs-*

Aa
--fs-xs
12px
Aa
--fs-sm
14px
Aa
--fs-base
16px
Aa
--fs-lg
18px
Aa
--fs-xl
20px
Aa
--fs-2xl
24px
Aa
--fs-3xl
30px
Aa
--fs-4xl
36px
Aa
--fs-5xl
48px
.label   { font-size: var(--fs-xs); text-transform: uppercase; }
.body    { font-size: var(--fs-base); line-height: 1.6; }
.heading { font-size: var(--fs-3xl); font-weight: 800; }
4

Font Families — --font-*

--font-sans → Inter (body)
--font-heading → Poppins (headings)
--font-mono → Roboto Mono (code)
Sans-serif
The quick brown fox
--font-interInter
The quick brown fox
--font-robotoRoboto
The quick brown fox
--font-poppinsPoppins
The quick brown fox
--font-montserratMontserrat
The quick brown fox
--font-outfitOutfit
The quick brown fox
--font-jakartaPlus Jakarta Sans
The quick brown fox
--font-dm-sansDM Sans
The quick brown fox
--font-nunitoNunito
The quick brown fox
--font-spaceSpace Grotesk
The quick brown fox
--font-manropeManrope
The quick brown fox
--font-figtreeFigtree
The quick brown fox
--font-soraSora
Monospace
const x = 42;
--font-roboto-monoRoboto Mono
const x = 42;
--font-jetbrainsJetBrains Mono
const x = 42;
--font-fira-codeFira Code
💡
Change the active font for the whole site by overriding --font-sans, --font-heading or --font-mono in your own CSS — all components update automatically.
5

Radius & Shadows — --radius-* / --shadow-*

--radius-none
0
--radius-sm
6px
--radius-md
12px
--radius-lg
20px
--radius-full
9999px
.btn   { border-radius: var(--radius-sm); box-shadow: var(--shadow-sm); }
.card  { border-radius: var(--radius-md); box-shadow: var(--shadow-md); }
.modal { border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); }
.glow  { box-shadow: var(--shadow-primary); }
5

Transitions — --ease-*

var(--ease-fast)  /* 0.1s ease — for hover states */
var(--ease)       /* 0.2s ease — standard default */
var(--ease-slow)  /* 0.4s ease — smooth reveals */

.btn  { transition: all var(--ease); }
.card { transition: transform var(--ease), box-shadow var(--ease); }
6

Full component — tokens only

Live result
New round
Aviator ×5.2
Bet: $10 → Win: $52
CSS
.game-card {
  padding: var(--space-6);
  border-radius: var(--radius-md);
  background: var(--color-dark-secondary);
  box-shadow: var(--shadow-md);
}
.game-card__label {
  font-size: var(--fs-xs);
  color: var(--color-primary);
}
.game-card__title {
  font-size: var(--fs-2xl);
  font-weight: 800;
  color: var(--color-light);
}
Reference

Utility Classes

Tailwind-compatible utility classes for fast layout. All in _utils.css.

📦 Display
.flex.flex-col.grid.inline-flex.block.hidden.inline-block
📍 Flex helpers
.flex-center.flex-between.flex-col-center.flex-1.items-center.justify-between
📏 Spacing
.m-4.mt-6.mb-4.mx-auto.p-4.px-6.py-8.gap-4
🔤 Typography
.text-primary.text-accent.text-muted.text-center.text-sm.text-xl.uppercase.font-bold
🔲 Sizing
.w-full.w-auto.h-full.min-h-screen.max-w-lg.container
💫 Effects
.transition.opacity-50.shadow-md.blur-sm.grayscale.rotate-45
Usage example
<div class="flex items-center gap-4 p-6">
  <button class="btn btn-primary">Action</button>
  <p class="text-sm text-muted">Helper text</p>
</div>

<div class="flex-center gap-6 mt-8 mb-4">
  <span class="tag tag-primary uppercase text-xs">New</span>
  <h2 class="text-2xl font-bold text-primary">Title</h2>
</div>
Reference

Animations

Built-in CSS animations from _animations.css. Just add a class — no JavaScript.

Fade
.fade-in
.fade-out
Scale
.scale-in
.scale-up
Slide
.slide-in
.slide-down
Bounce
.bounce
.bounce-in
Pulse
.animate-pulse
.pulse-slow
Float
.animate-float
Glow
.animate-glow
.glow-hover
Spin
.spin
.spin-slow
Shake
.shake
.shake-error
Neon
.animate-neon
.animate-neon-blue
Rainbow
.animate-rainbow
.animate-breathe
Delay / Speed
.anim-delay-1
.anim-fast · .anim-slow
<div class="fade-in">Fades in on load</div>
<button class="btn btn-primary animate-pulse">Pulsing CTA</button>
<div class="animate-float">Floating element</div>
<span class="animate-neon text-accent">Neon glow text</span>
<div class="scale-in anim-delay-1">Delayed scale-in</div>
Project

File Structure

All CSS is modular — import everything via style.css or pick individual files.

njx-ui/
├── style.css              ← entry point (@import all modules)
├── css/
│   ├── _base.css              ← design tokens (CSS variables)
│   ├── _reset.css             ← CSS reset
│   ├── _global.css            ← 9 theme definitions
│   ├── _grid.css              ← .row/.col-* and .grid-col-*
│   ├── _typography.css        ← headings, text helpers
│   ├── _utils.css             ← Tailwind-style utility classes
│   ├── _buttons.css           ← .btn, .btn-primary, gradients, glow
│   ├── _cards.css             ← .card, .card-glass, .card-glow, .box
│   ├── _form.css              ← .inp, .toggle, .check, .float-field
│   ├── _tags.css              ← .tag, .badge, outline variants
│   ├── _nav.css               ← .nav, solid/glass/dark/floating/underline
│   ├── _tab.css               ← .tab-wrap, pills/boxed/card variants
│   ├── _collapse.css          ← .collapse, accordion, accordion-flush
│   ├── _dropdown.css          ← .dropdown, positions, JS+native API
│   ├── _slider.css            ← .carousel, scroll-snap, dots, gap
│   ├── _popups.css            ← .lib-modal-*, success/error/warning/info
│   ├── _links.css             ← .link, slide/arrow/btn/card/list variants
│   ├── _notifications.css     ← .lib-toast, alerts
│   ├── _table.css             ← .table and variants
│   ├── _breadcrumb.css        ← .breadcrumb
│   ├── _sections.css          ← .section, .panel, layout sections
│   ├── _animations.css        ← fade, pulse, float, neon, etc.
│   ├── _gradients.css         ← bg-gradient-*, text-gradient-*
│   └── _hovers.css            ← hover-lift, hover-glow, etc.
└── js/
    └── njx.js             ← collapse, tabs, modal, carousel…
💡
Granular imports: Each file is independent. If you only need buttons and cards, import just _base.css + _themes.css + _buttons.css + _cards.css and save several KB.
Project

Examples

Common patterns — copy and adapt.

Card with actions
<div class="card card-glass p-6">
  <h3 class="text-gradient-primary">Card Title</h3>
  <p class="text-muted mt-2">Description text...</p>
  <div class="flex gap-4 mt-6">
    <button class="btn btn-primary btn-sm">Action</button>
    <button class="btn btn-outline btn-sm">Cancel</button>
  </div>
</div>
Form with validation states
<div class="field">
  <label class="field-label">Email</label>
  <input class="inp inp-error" type="email" placeholder="[email protected]">
  <span class="field-error-msg">Invalid email address</span>
</div>
<div class="field">
  <label class="field-label">Password</label>
  <input class="inp inp-success" type="password">
  <span class="field-success-msg">Password accepted</span>
</div>
<div class="lib-modal-overlay" id="modal">
  <div class="lib-modal-box is-success">
    <span class="lib-modal-icon"></span>
    <div class="lib-modal-title">Success!</div>
    <div class="lib-modal-text">Operation completed</div>
    <div class="lib-modal-actions">
      <button class="btn btn-success"
        onclick="document.getElementById('modal').remove()">OK</button>
    </div>
  </div>
</div>
<div x-data="{ open: true }" @keydown.escape.window="open = false">
  <div class="lib-modal-overlay" :class="{ open }" @click.self="open = false">
    <div class="lib-modal-box is-success">
      <span class="lib-modal-icon"></span>
      <div class="lib-modal-title">Success!</div>
      <div class="lib-modal-text">Operation completed</div>
      <div class="lib-modal-actions">
        <button class="btn btn-success" @click="open = false">OK</button>
      </div>
    </div>
  </div>
</div>