- The navbar component provides a responsive navigation
- header for your website or application with full
- desktop, tablet, and mobile support.
-
-
-
-
Basic Navbar
-
- A responsive Bootstrap 5 navbar with brand,
- collapsible nav menu, dropdown, disabled link, and
- search form. Uses navbar-expand-lg so
- the menu collapses on tablet and mobile.
-
-
-
-
-
- Copy
-
-
<nav class="navbar navbar-expand-lg shadow-sm mb-3">
- <div class="container-fluid">
- <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%;">
- <a class="navbar-brand" href="#">
- <strong>Tani</strong>
- </a>
- <button class="navbar-toggler" type="button"
- data-bs-toggle="collapse"
- data-bs-target="#navbarSupportedContent"
- aria-controls="navbarSupportedContent" aria-expanded="false"
- aria-label="Toggle navigation">
- <span class="navbar-toggler-icon"></span>
- </button>
- </div>
- <div class="collapse navbar-collapse" id="navbarSupportedContent">
- <ul class="navbar-nav me-auto mb-2 mb-lg-0">
- <li class="nav-item">
- <a class="nav-link active" aria-current="page" href="#">Home</a>
- </li>
- <li class="nav-item">
- <a class="nav-link" href="#">Features</a>
- </li>
- <li class="nav-item">
- <a class="nav-link" href="#">Pricing</a>
- </li>
- <li class="nav-item dropdown">
- <a class="nav-link dropdown-toggle" href="#" role="button"
- data-bs-toggle="dropdown" aria-expanded="false">
- Components
- </a>
- <ul class="dropdown-menu">
- <li><a class="dropdown-item" href="#">Buttons</a></li>
- <li><a class="dropdown-item" href="#">Cards</a></li>
- <li><a class="dropdown-item" href="#">Modals</a></li>
- <li><hr class="dropdown-divider" /></li>
- <li><a class="dropdown-item" href="#">All components</a></li>
- </ul>
- </li>
- <li class="nav-item">
- <a class="nav-link disabled" aria-disabled="true">Disabled</a>
- </li>
- </ul>
- <form class="d-flex" role="search">
- <input class="form-control me-2" type="search" placeholder="Search docs" aria-label="Search" />
- <button class="btn btn-outline-primary" type="submit">Search</button>
- </form>
- </div>
+Skip to content
+
+
+
+
+
+ Docs / Tani CSS Framework
+
+Introduction #
+
+
+
⚡ Zero-JS
🎨 OKLCH Colors
🌗 Dark Mode
🌐 RTL
+
The CSS framework that needs no JavaScript .
+
Tani is a lightweight, utility-first CSS framework with God-Mode features: scroll-driven animations, container queries, glassmorphism, gold accents and a complete zero-JS component library. Drop in one file and build premium interfaces.
+
+
+
+
🚀
Zero Build Link a single stylesheet. No CLI, no config, no toolchain.
+
🪄
Zero JS Components Modals, tabs, dropdowns, popovers, carousels — pure CSS.
+
🌈
OKLCH Colors Perceptually uniform palette with auto-generated hover states.
+
📦
Container Queries Components that adapt to their container, not the viewport.
+
✨
Scroll Animations Elements fade in as you scroll — no JavaScript required.
+
💎
Premium Polish Glassmorphism, gold accents and gradient text built in.
+
+
+
+
+Installation #
+CDN Copy
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/TaniCSS/Tani@v2.3.1/dist/css/tani.min.css">Download Copy
<link rel="stylesheet" href="./dist/css/tani.css">NPM 💡 Tani is a single static CSS file. There is nothing to compile — just link it and start using classes.
+
+
+Quick Start #
+Copy
<!DOCTYPE html>
+<html lang="en" class="dark-mode">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <link rel="stylesheet" href="./dist/css/tani.css">
+</head>
+<body>
+ <div class="container">
+ <h1 class="text-center">Hello, Tani!</h1>
+ <button class="btn btn-primary">Click Me</button>
</div>
-</nav>
-
-
-
-
-
-
Colored Navbars
-
-
-
-
- Copy
-
-
-<nav class="navbar navbar-expand-lg navbar-primary mb-3"></nav>
-<nav class="navbar navbar-expand-lg navbar-dark mb-3"></nav>
-<nav class="navbar navbar-expand-lg navbar-light"></nav>
-
-
-
-
-
-
- Responsive Behavior
-
-
- The navbar automatically adapts to different screen
- sizes:
-
-
-
- Desktop (> 992px) : Full
- horizontal navigation
-
-
- Tablet (≤ 992px) : Toggler
- button appears, collapsible menu
-
-
- Mobile (≤ 768px) : Dropdown menu
- with stacked items
-
-
-
- Note: Resize your browser window to
- see the responsive behavior in action.
-
-
-
-
-
JavaScript
-
- The Tani navbar uses Bootstrap 5 data attributes
- (data-bs-toggle /
- data-bs-target) for toggling and
- dropdown functionality. Here's the JavaScript that
- powers it:
-
-
-
-
- Copy
-
-
// Navbar toggler functionality (Bootstrap 5)
-document.addEventListener('DOMContentLoaded', function () {
- const togglerButtons = document.querySelectorAll('.navbar-toggler');
- togglerButtons.forEach(button => {
- button.addEventListener('click', function () {
- const targetId = this.getAttribute('data-bs-target') || this.getAttribute('data-target');
- const target = targetId ? document.querySelector(targetId) : this.closest('.navbar').querySelector('.navbar-collapse');
- if (target) {
- const isExpanded = target.classList.contains('show');
- target.classList.toggle('show');
- this.setAttribute('aria-expanded', !isExpanded);
- }
- });
- });
-
- // Dropdown toggle functionality
- const dropdownToggles = document.querySelectorAll('.dropdown-toggle, [data-bs-toggle="dropdown"]');
- dropdownToggles.forEach(toggle => {
- toggle.addEventListener('click', function (e) {
- e.preventDefault();
- const parent = this.closest('.nav-item.dropdown, .btn-group.dropdown');
- if (parent) {
- parent.classList.toggle('show');
- }
- });
- });
-});
-
-
-
-
Tip: For production, use the
- official
-
Bootstrap Bundle
- which includes Popper.js and handles all toggling
- automatically with
-
data-bs-toggle attributes — no custom
- JavaScript needed.
-
-
-
-
-
-
- Navs & Tabs
-
- Navigation components for creating tabbed interfaces,
- pills, and other navigation patterns.
-
-
-
-
Base Nav
-
-
-
-
- Copy
-
-
<ul class="nav">
-<li class="nav-item">
-<a class="nav-link active" href="#">Active</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#">Link</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#">Link</a>
-</li>
-<li class="nav-item">
-<a class="nav-link disabled" href="#">Disabled</a>
-</li>
-</ul>
-
-
-
-
-
-
Tabs
-
-
-
-
-
-
- This is the home tab content. Tabs
- are great for organizing content
- into separate views.
-
-
-
-
- This is the profile tab content.
- Click on different tabs to see the
- content change.
-
-
-
-
-
-
-
- Copy
-
-
<ul class="nav nav-tabs">
-<li class="nav-item">
-<a class="nav-link active" href="#" data-tab="home">Home</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#" data-tab="profile">Profile</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#" data-tab="contact">Contact</a>
-</li>
-</ul>
-<div class="tab-content">
-<div class="tab-pane active" id="home">
-<p>This is the home tab content.</p>
-</div>
-<div class="tab-pane" id="profile">
-<p>This is the profile tab content.</p>
-</div>
-<div class="tab-pane" id="contact">
-<p>This is the contact tab content.</p>
-</div>
-</div>
-
-
-
-
-
-
Pills
-
-
-
-
-
-
- Pills provide a more rounded,
- button-like appearance for
- navigation.
-
-
-
-
Profile content in pills style.
-
-
-
-
-
-
- Copy
-
-
<ul class="nav nav-pills">
-<li class="nav-item">
-<a class="nav-link active" href="#" data-tab="pill-home">Home</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#" data-tab="pill-profile">Profile</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#" data-tab="pill-contact">Contact</a>
-</li>
-</ul>
-<div class="tab-content">
-<div class="tab-pane active" id="pill-home">
-<p>Pills provide a more rounded, button-like appearance for navigation.</p>
-</div>
-<div class="tab-pane" id="pill-profile">
-<p>Profile content in pills style.</p>
-</div>
-<div class="tab-pane" id="pill-contact">
-<p>Contact content in pills style.</p>
-</div>
-</div>
-
-
-
-
-
-
Fill and Justify
-
-
-
-
- Copy
-
-
<!-- Fill: proportional width -->
-<ul class="nav nav-pills nav-fill">
-<li class="nav-item">
-<a class="nav-link active" href="#">Active</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#">Much longer nav link</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#">Link</a>
-</li>
-</ul>
-<!-- Justified: equal width -->
-<ul class="nav nav-pills nav-justified">
-<li class="nav-item">
-<a class="nav-link active" href="#">Active</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#">Much longer nav link</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#">Link</a>
-</li>
-</ul>
-
-
-
-
-
-
Vertical Nav
-
-
-
-
- Copy
-
-
<ul class="nav nav-pills nav-vertical">
-<li class="nav-item">
-<a class="nav-link active" href="#">Active</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#">Link</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" href="#">Link</a>
-</li>
-<li class="nav-item">
-<a class="nav-link disabled" href="#">Disabled</a>
-</li>
-</ul>
-
-
-
-
-
-
-
-
-
-
- Progress
-
- Progress bars are used to visualize the completion
- status of tasks or processes.
-
-
-
-
- Basic Progress Bar
-
-
-
-
-
- Copy
-
-
<div class="progress">
-<div class="progress-bar" style="width: 25%"></div>
-</div>
-<div class="progress">
-<div class="progress-bar" style="width: 50%"></div>
-</div>
-<div class="progress">
-<div class="progress-bar" style="width: 75%"></div>
-</div>
-<div class="progress">
-<div class="progress-bar" style="width: 100%"></div>
-</div>
-
-
-
-
-
-
- Progress Bar with Label
-
-
-
-
-
- Copy
-
-
<div class="progress">
-<div class="progress-bar" style="width: 25%">25%</div>
-</div>
-<div class="progress">
-<div class="progress-bar" style="width: 60%">60%</div>
-</div>
-
-
-
-
-
-
- Colored Progress Bars
-
-
-
-
-
- Copy
-
-
<div class="progress">
-<div class="progress-bar progress-bar-primary" style="width: 25%">Primary</div>
-</div>
-<div class="progress">
-<div class="progress-bar progress-bar-secondary" style="width: 35%">Secondary</div>
-</div>
-<div class="progress">
-<div class="progress-bar progress-bar-success" style="width: 50%">Success</div>
-</div>
-<div class="progress">
-<div class="progress-bar progress-bar-danger" style="width: 65%">Danger</div>
-</div>
-<div class="progress">
-<div class="progress-bar progress-bar-warning" style="width: 75%">Warning</div>
-</div>
-<div class="progress">
-<div class="progress-bar progress-bar-info" style="width: 85%">Info</div>
-</div>
-<div class="progress">
-<div class="progress-bar progress-bar-dark" style="width: 95%">Dark</div>
-</div>
-
-
-
-
-
-
- Striped Progress Bars
-
-
-
-
-
- Copy
-
-
<div class="progress">
-<div class="progress-bar progress-bar-striped" style="width: 40%"></div>
-</div>
-<div class="progress">
-<div class="progress-bar progress-bar-success progress-bar-striped" style="width: 60%"></div>
-</div>
-<div class="progress">
-<div class="progress-bar progress-bar-danger progress-bar-striped" style="width: 80%"></div>
-</div>
-
-
-
-
-
-
- Animated Progress Bars
-
-
-
-
-
- Copy
-
-
<div class="progress">
-<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 45%"></div>
-</div>
-<div class="progress">
-<div class="progress-bar progress-bar-success progress-bar-striped progress-bar-animated" style="width: 70%"></div>
-</div>
-
-
-
-
-
-
- Progress Bar Sizes
-
-
-
-
-
- Copy
-
-
<!-- Small -->
-<div class="progress progress-sm">
-<div class="progress-bar" style="width: 50%"></div>
-</div>
-<!-- Normal -->
-<div class="progress">
-<div class="progress-bar" style="width: 50%"></div>
-</div>
-<!-- Large -->
-<div class="progress progress-lg">
-<div class="progress-bar" style="width: 50%">50%</div>
-</div>
-<!-- Extra Large -->
-<div class="progress progress-xl">
-<div class="progress-bar" style="width: 50%">50%</div>
-</div>
-
-
-
-
-
-
- Multiple Progress Bars
-
-
-
-
-
- 15%
-
-
- 30%
-
-
- 20%
-
-
-
-
-
- Copy
-
-
<div class="progress">
-<div class="progress-bar progress-bar-primary" style="width: 15%">15%</div>
-<div class="progress-bar progress-bar-success" style="width: 30%">30%</div>
-<div class="progress-bar progress-bar-info" style="width: 20%">20%</div>
-</div>
-
-
-
-
-
- Note: Use the
- style="width: X%" attribute to set the
- progress value. You can dynamically update this with
- JavaScript to create animated progress indicators.
-
-
-
-
- Dynamic Progress Example
-
-
-
-
-
- Start Progress
-
-
- Reset
-
-
-
-
- Copy
-
-
function startProgress() {
- const progressBar = document.getElementById('dynamicProgress');
- let width = 0;
- const interval = setInterval(() => {
- if (width >= 100) {
- clearInterval(interval);
- } else {
- width++;
- progressBar.style.width = width + '%';
- progressBar.textContent = width + '%';
- }
- }, 20);
-}
-
-function resetProgress() {
- const progressBar = document.getElementById('dynamicProgress');
- progressBar.style.width = '0%';
- progressBar.textContent = '0%';
-}
-
-
-
-
-
-
-
- RTL/LTR Support
-
- Tani has native RTL support using
- CSS Logical Properties. No extra classes or
- configuration needed!
-
-
-
-
How It Works
-
- All spacing utilities use logical properties that
- automatically flip based on text direction:
-
-
-
-
-
- Copy
-
-
/* These automatically flip in RTL mode */
-.container {
- margin-inline: auto; /* margin-left/right in LTR, flips in RTL */
- padding-inline: 1rem; /* padding-left/right in LTR, flips in RTL */
+</body>
+</html>That is the entire setup. Every class in this documentation is available immediately.
+
+
+Design Tokens #
+Design Tokens Tani is driven by CSS custom properties — change a handful of variables to re-skin your entire app.
Copy
:root {
+ --primary: oklch(70% 0.18 70);
+ --radius-md: 8px;
+ --shadow-md: 0 4px 6px -1px oklch(0% 0 0 / 0.1);
+ --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
+}
+
+
+Colors #
+Semantic colors come with automatic dark-mode and hover variants via relative OKLCH.
secondary
var(--secondary)
Copy
<div class="badge badge-primary">Primary</div>
+<div class="text-success">Success text</div>
+<div class="bg-warning" style="padding:1rem;border-radius:8px;">Warning surface</div>
+
+
+Spacing & Typography #
+Utility Value m-1 / p-10.25remm-2 / p-20.5remm-3 / p-30.75remm-4 / p-41remm-5 / p-51.25remm-6 / p-61.5rem
Copy
.text-3xl { font-size: clamp(1.875rem, 1.5rem + 1.875vw, 3rem); }
+.leading-relaxed { line-height: 1.625; }
+
+
+Utility Reference #
+Layout & Flex Copy
<div class="container">…</div>
+<div class="d-flex justify-content-between align-items-center">…</div>
+<div class="grid grid-cols-12 gap-4">…</div>Sizing & Position Copy
<div class="w-100 h-50 position-relative">…</div>
+<div class="top-0 start-0 z-50">…</div>Borders, Radius & Shadow Copy
<div class="border border-primary rounded-lg shadow-lg">…</div>
+<div class="glow gold-glow">…</div>Effects & Transforms Copy
<div class="scale-105 rotate-45 translate-x-2 transition">…</div>
+<div class="hover-lift">3D lift</div>New in v2.2 Copy
<div class="glass">Glass</div>
+<div class="gold-text-gradient">Gold gradient</div>
+<div class="ring-2 ring-primary">Ring</div>
+<div class="divide-y">…</div>
+<div class="space-x-4">…</div>
+<div class="line-clamp-2">…</div>
+
+
+All Components #
+Buttons Primary Outline Ghost Rounded
preview
Live demo Copy
<button class="btn btn-primary">Primary</button>
+<button class="btn btn-outline-primary">Outline</button>Badges primary secondary success danger warning info dark
preview
Live demo Copy
<span class="badge badge-primary">Primary</span>Alerts Success! Saved.
Something went wrong.
preview
Live demo Copy
<div class="alert alert-success">Success!</div>Cards Card title A premium card with hover lift.
Go preview
Live demo Copy
<div class="card"><div class="card-body">…</div></div>Forms Live demo Copy
<input class="form-control" placeholder="Email">Tabs (zero-JS) Live demo Copy
<div class="tabs">…</div>Dropdown (focus) Live demo Copy
<div class="dropdown">…<ul class="dropdown-menu">…</ul></div>Modal (native dialog) Live demo Copy
<dialog class="modal" id="m"><div class="modal-content">…</div></dialog>Toast Live demo Copy
<div class="toast show toast-primary">…</div>Accordion Click to expand Native details element, zero JS.
preview
Live demo Copy
<details class="accordion-item"><summary>…</summary><div>…</div></details>Tooltip Live demo Copy
<button data-tooltip="Hi">Hover</button>
+<button data-tooltip="Hi" data-tooltip-pos="bottom">Bottom</button>Avatars Live demo Copy
<div class="avatar">AB</div>Timeline & Steps Live demo Copy
<ol class="timeline">…</ol>List group & Input group Live demo Copy
<ul class="list-group">…</ul>
+
+
+Popover #
+Trigger on :hover or :focus. Four directions, glass + gold variants.
Top Appears above.
+Bottom Appears below.
+Glass Blurred.
+Gold Premium glow.
preview
Live demo Copy
<span class="popover-trigger">
+ <button class="btn btn-primary">Top</button>
+ <span class="popover popover-top" role="tooltip">
+ <span class="popover-header">Title</span>
+ <span class="popover-body">Body</span>
+ </span>
+</span>Copy
.popover-start { inset-inline-end: calc(100% + .5rem); top: 50%; translate: 0 -50%; }
+.popover-glass { backdrop-filter: blur(14px); }
+.popover-gold { border-color: var(--gold); }
+
+
+Carousel #
+Scroll-snap with anchor arrows/dots and touch swipe, plus an autoplay marquee that pauses on hover.
Live demo Copy
<div class="carousel carousel-cols-1">
+ <a class="carousel-arrow carousel-prev" href="#s1">‹</a>
+ <div class="carousel-viewport"><div class="carousel-track">
+ <div id="s1" class="carousel-slide">…</div>
+ </div></div>
+</div>Autoplay marquee (duplicate slides, pauses on hover):
Live demo Copy
<div class="carousel carousel-auto">
+ <div class="carousel-viewport"><div class="carousel-track">
+ <!-- duplicate slides for seamless loop -->
+ </div></div>
+</div>
+
+
+Animations #
+All animations honor prefers-reduced-motion.
fade-in slide-up scale-in pulse bounce float hover-lift
preview
Live demo Copy
<div class="animate-fade-in">…</div>
+<div class="animate-slide-up">…</div>
+<div class="hover-lift">3D on hover</div>Marquee & typewriter:
★ Zero-JS ★ Container Queries ★ Glassmorphism ★ Gold ★ Subgrid ★
Typed with CSS steps preview
Live demo Copy
<div class="marquee"><span class="marquee-content">…</span></div>
+<span class="typewriter" style="--tw-chars:20">Text</span>Copy
.animate-spin { animation: spinner-border 1s linear infinite; }
+.marquee-content { animation: marqueeScroll 18s linear infinite; }
+.typewriter { animation: typewriter 3s steps(var(--tw-chars)) forwards; }
+
+
+Responsive #
+Breakpoints Prefix Min width Example sm-640px sm-flex, sm-grid-cols-12md-768px md-flex, md-grid-cols-12lg-992px lg-flex, lg-grid-cols-12xl-1200px xl-flex, xl-grid-cols-12xxl-1400px xxl-flex, xxl-grid-cols-12
Container Queries Wrap a block in .cq and use .cq-md-* utilities that respond to the container.
TN
Container-aware Flexes at ≥560px container width.
preview
Live demo Copy
<div class="cq">
+ <div class="cq-md-flex">…</div>
+</div>
+
+
+RTL & Dark Mode #
+Tani is built on logical properties , so RTL works automatically. Dark mode is native and needs no toggle (system preference) — or force it with the .dark-mode class.
Copy
<div dir="rtl" style="margin-inline-start:2rem;background:var(--surface);padding:1rem;border-radius:8px">
+ This box uses <code>margin-inline-start</code> — it flips to the right in RTL automatically.
+</div>Copy
/* force themes */
+<html class="dark-mode"> /* dark */
+<html class="light-mode"> /* light */
+
+
+Customization #
+Override tokens or extend with your own classes. Because Tani is plain CSS, you can layer your theme after the framework import.
Copy
:root {
+ --primary: oklch(65% 0.2 145); /* your brand */
+ --radius-md: 12px;
+ --gold: oklch(80% 0.12 60);
}
-
-.text-start {
- text-align: start; /* left in LTR, right in RTL */
-}
-
-.mr-3 {
- margin-inline-end: 1rem; /* margin-right in LTR, margin-left in RTL */
-}
-
-
-
-
-
Enable RTL
-
- Just set the dir attribute on your
- HTML:
-
-
-
-
-
- Copy
-
-
<!-- For RTL languages (Arabic, Hebrew, Persian, etc.) -->
-<html lang="ar" dir="rtl">
- <!-- Your content -->
-</html>
-
-<!-- For LTR languages (English, etc.) -->
-<html lang="en" dir="ltr">
- <!-- Your content -->
-</html>
-
-
-
-
- Note: RTL support is built into
- the core. You don't need a separate RTL
- stylesheet or configuration.
-
-
-
-
-
-
-
+
Design tokens
+
Adjust the framework variables — the preview re-themes live.
+
+
Spacing scale
+
One unit = 0.25rem. Visualized as box sizes (1u,2u,3u…).
+
+
Typography scale
+
+
+
+
+Templates #
+Ten production-ready page templates built with nothing but Tani . Preview renders live; download a standalone HTML file (links the CDN build) with one click.
+
+
+Learn #
+Beginner tutorial
+
+ 1 Link Tani Add one <link> to your page. No npm, no bundler.
+ 2 Use a utility Classes like d-flex, p-4, text-center compose your layout.
+ 3 Add a component Buttons, cards and alerts are ready-made classes.
+ 4 Theme it Override --primary and friends in :root.
+ 5 Ship That is the whole framework. Open the playground to experiment.
+
+Advanced: custom theming
+Because Tani is plain CSS variables, you can generate an entire brand theme from a single hue. Example — rotate the primary around the OKLCH wheel:
+Copy
:root {
+ --primary: oklch(68% 0.18 275); /* indigo */
+ --primary-soft: oklch(68% 0.18 275 / 0.12);
+ --gold: oklch(82% 0.12 85);
+ --radius-md: 14px;
+}
+/* dark surfaces auto-adjusted via relative color */
+Use oklch(from var(--primary) l c h / alpha) to derive tints without hand-picking hex values.
+Test your knowledge
+
+
1. What does Tani require to render components?
+ A build step
+ A JavaScript runtime
+ Just a linked stylesheet
+
2. Which property drives theming?
+ --theme-color
+ CSS custom properties in :root
+ A JSON config
+
3. How do container queries activate?
+ Wrap content in .cq
+ Add a media query
+ Use JavaScript
+
+
+
+
+
+FAQ #
+Is Tani really zero-JavaScript? Yes for all components. A few conveniences (toast demo, modal close) use tiny native APIs, but nothing is required to render the UI.
Does it work without a build step? Absolutely. Link the stylesheet and you are done.
How big is it? ~55KB raw, ~15KB gzipped for the full framework.
Browser support? All modern browsers. Cutting-edge features (scroll animations, anchor positioning, container queries) degrade gracefully.
How do I use a custom font? Set --font-sans in :root to any font-family string.
Can I use Tani with React / Vue / Svelte? Yes — Tani is framework-agnostic. Class names are just strings in your markup.
Does Tani ship a reset? Yes, a modern normalize is included so spacing and forms are consistent cross-browser.
How do I disable an animation for reduced motion? Tani already wraps motion in @media (prefers-reduced-motion: no-preference). You do nothing.
Is there a dark-mode toggle component? Dark mode follows the OS by default; add .dark-mode/.light-mode on <html> to force it (this docs site includes a toggle).
Where do I report bugs or contribute? Open an issue or PR on GitHub . See the Contributors page.
+
+
+
+
+
+
+
+
+
diff --git a/license.html b/license.html
new file mode 100644
index 0000000..8027936
--- /dev/null
+++ b/license.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+License
+Tani is released under the MIT License — free for personal and commercial use.
+MIT License
+
+Copyright (c) 2024 Tani CSS Framework
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/smoke_selenium.py b/tests/smoke_selenium.py
new file mode 100644
index 0000000..b03cabf
--- /dev/null
+++ b/tests/smoke_selenium.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+"""Selenium smoke test for Tani — verifies rendered computed styles,
+responsive breakpoints, dark-mode toggle, RTL logical spacing, and screenshots key pages.
+Portable: repo root is derived from this file's location; Chrome/Chromium binaries
+are auto-detected or overridden via TANI_CHROME / TANI_CHROMEDRIVER env vars."""
+import os, sys, shutil, time
+from selenium import webdriver
+from selenium.webdriver.chrome.options import Options
+from selenium.webdriver.chrome.service import Service
+from selenium.webdriver.common.by import By
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+URL = "file://" + os.path.join(ROOT, "tests", "visual", "all-components.html")
+DOCS = "file://" + os.path.join(ROOT, "index.html")
+SHOTS = os.path.join(ROOT, "tests", "visual", "shots")
+os.makedirs(SHOTS, exist_ok=True)
+
+def find_bin(env_var, *candidates):
+ if os.environ.get(env_var):
+ return os.environ[env_var]
+ for c in candidates:
+ p = shutil.which(c) or (c if os.path.exists(c) else None)
+ if p:
+ return p
+ return None
+
+chrome_bin = find_bin("TANI_CHROME", "chromium", "chromium-browser", "google-chrome", "chrome")
+driver_bin = find_bin("TANI_CHROMEDRIVER", "chromedriver")
+if not chrome_bin or not driver_bin:
+ sys.exit("Chrome/Chromium and chromedriver are required "
+ "(set TANI_CHROME / TANI_CHROMEDRIVER to override).")
+
+o = Options()
+o.binary_location = chrome_bin
+o.add_argument("--headless=new")
+o.add_argument("--no-sandbox")
+o.add_argument("--disable-dev-shm-usage")
+o.set_capability("goog:loggingPrefs", {"browser": "ALL"})
+d = webdriver.Chrome(service=Service(driver_bin), options=o)
+
+results = []
+def check(name, cond, detail=""):
+ results.append((bool(cond), name, detail))
+ print(("PASS " if cond else "FAIL ") + name + ((" -> " + detail) if detail and not cond else ""))
+
+def cs(el, prop):
+ return d.execute_script("return getComputedStyle(arguments[0]).getPropertyValue(%r)" % prop, el)
+
+def el(id_):
+ return d.find_element(By.ID, id_)
+
+d.get(URL)
+d.set_window_size(1200, 1000)
+
+# ---- FILTERS ----
+b = el("t-blur"); check("filter blur-sm", "blur(4px)" in cs(b, "filter"), cs(b, "filter"))
+g = el("t-gray"); check("filter grayscale", "grayscale" in cs(g, "filter"), cs(g, "filter"))
+c = el("t-combo"); f = cs(c, "filter")
+check("filter combo (blur+bright+saturate)", "blur(12px)" in f and "brightness(1.5)" in f and "saturate(2)" in f, f)
+bd = el("t-bd"); bf = cs(bd, "backdrop-filter")
+check("backdrop-blur", "blur(8px)" in bf, bf)
+
+# ---- TRANSFORMS ----
+r = el("t-rot"); check("transform rotate-45", "45deg" in cs(r, "rotate"), cs(r, "rotate"))
+s = el("t-scl"); check("transform scale-125", "1.25" in cs(s, "scale"), cs(s, "scale"))
+tx = el("t-trx"); t = cs(tx, "translate")
+check("transform translate-x-4 (16px)", "16px" in t, t)
+sk = el("t-skew"); check("transform skew-y-6 (matrix)", "matrix" in cs(sk, "transform") and "none" not in cs(sk, "transform"), cs(sk, "transform"))
+c2 = el("t-combo2"); check("transform combo2 rotate-12", "12deg" in cs(c2, "rotate"), cs(c2, "rotate"))
+
+# ---- ANIMATIONS ----
+for eid, expect in [("t-ping","ping"),("t-heart","heartBeat"),("t-float","floatY"),("t-spin","spinSlow"),("t-fade","fadeInUp"),("t-shake","shakeX")]:
+ an = cs(el(eid), "animation-name")
+ check("animation %s" % eid, expect in an, an)
+
+# ---- FIGURE ----
+fg = el("t-figure"); check("figure margin 0", cs(fg, "margin-top") == "0px", cs(fg, "margin"))
+
+# ---- FORM validation ----
+vf = el("t-vf"); check("valid-feedback visible (is-valid)", cs(vf, "display") == "block", cs(vf, "display"))
+
+# ---- PROGRESS (target the inner .progress-bar, not the .progress container) ----
+p1 = d.find_element(By.CSS_SELECTOR, "#t-prog1 .progress-bar"); check("progress striped gradient", "gradient" in cs(p1, "background-image"), cs(p1, "background-image")[:40])
+p2 = d.find_element(By.CSS_SELECTOR, "#t-prog2 .progress-bar"); check("progress animated name", "progressStripes" in cs(p2, "animation-name"), cs(p2, "animation-name"))
+
+# ---- OFFCANVAS ----
+oc = el("t-oc")
+check("offcanvas position fixed", cs(oc, "position") == "fixed", cs(oc, "position"))
+t0 = cs(oc, "transform")
+check("offcanvas hidden by default (transformed)", t0 not in ("none", "matrix(1, 0, 0, 1, 0, 0)", "matrix(1,0,0,1,0,0)"), t0)
+# toggle open (checkbox is d-none, so click via JS); wait for 0.3s transition
+d.execute_script("document.getElementById('t-ocToggle').click()")
+time.sleep(0.5)
+t1 = cs(oc, "transform")
+check("offcanvas opens on toggle (transform none/identity)", t1 in ("none", "matrix(1, 0, 0, 1, 0, 0)", "matrix(1,0,0,1,0,0)"), t1)
+check("offcanvas backdrop shown", cs(el("t-ocb"), "display") == "block", cs(el("t-ocb"), "display"))
+
+# ---- RESPONSIVE (CDP viewport override — reliable for media queries in headless) ----
+resp = el("t-resp")
+def set_vp(w, h):
+ d.execute_cdp_cmd("Emulation.setDeviceMetricsOverride",
+ {"width": w, "height": h, "deviceScaleFactor": 1, "mobile": False})
+ time.sleep(0.2)
+set_vp(600, 800); check("responsive d-none below md (600px)", cs(resp, "display") == "none", cs(resp, "display"))
+set_vp(820, 900); check("responsive d-md-block at md+ (820px)", cs(resp, "display") == "block", cs(resp, "display"))
+set_vp(1200, 1000)
+
+# ---- DARK MODE (force light scheme first so the manual toggle is observable) ----
+d.execute_cdp_cmd("Emulation.setEmulatedMedia", {"media": "screen",
+ "features": [{"name": "prefers-color-scheme", "value": "light"}]})
+time.sleep(0.1)
+card = el("t-card")
+before = cs(card, "background-color")
+d.execute_script("arguments[0].click()", el("t-darkbtn"))
+after = cs(card, "background-color")
+check("dark-mode toggles card surface", before != after, "%s -> %s" % (before, after))
+# restore light for screenshot consistency
+d.execute_script("arguments[0].click()", el("t-darkbtn"))
+d.execute_cdp_cmd("Emulation.setEmulatedMedia", {"media": "screen",
+ "features": [{"name": "prefers-color-scheme", "value": "dark"}]})
+
+# ---- RTL ----
+rtl = el("t-rtl")
+mr = cs(rtl, "margin-right"); ml = cs(rtl, "margin-left")
+check("RTL ms-4 maps to right (16px)", mr == "16px", "mr=%s ml=%s" % (mr, ml))
+
+# ---- SCREENSHOTS ----
+d.get(URL); d.set_window_size(1200, 1400); d.execute_script("window.scrollTo(0,0)")
+d.save_screenshot(os.path.join(SHOTS, "all-components-light.png"))
+d.execute_script("arguments[0].click()", d.find_element(By.ID, "t-darkbtn"))
+d.save_screenshot(os.path.join(SHOTS, "all-components-dark.png"))
+d.execute_script("arguments[0].click()", d.find_element(By.ID, "t-darkbtn"))
+d.set_window_size(390, 800)
+d.save_screenshot(os.path.join(SHOTS, "all-components-mobile.png"))
+
+# ---- real docs page: console errors + screenshot ----
+d.set_window_size(1280, 1600)
+d.get(DOCS)
+logs = d.get_log("browser")
+errs = [l for l in logs if l.get("level") == "SEVERE"]
+check("docs index.html has no SEVERE console errors", not errs, str(errs[:3]))
+d.save_screenshot(os.path.join(SHOTS, "docs-index.png"))
+d.set_window_size(390, 800); d.get(DOCS); d.save_screenshot(os.path.join(SHOTS, "docs-index-mobile.png"))
+
+d.quit()
+
+passed = sum(1 for r in results if r[0])
+print("\n==== SUMMARY: %d/%d passed ====" % (passed, len(results)))
+for ok, name, det in results:
+ if not ok:
+ print(" FAIL:", name, det)
+sys.exit(0 if passed == len(results) else 1)
diff --git a/tests/visual/all-components.html b/tests/visual/all-components.html
new file mode 100644
index 0000000..8129e5a
--- /dev/null
+++ b/tests/visual/all-components.html
@@ -0,0 +1,129 @@
+
+
+
+