@aravi1008/ui
v1.4.0
Published
A personal professional-grade design system — global CSS, themes, icons, utilities, components. One install, full project setup.
Maintainers
Readme
@aravi1008/ui
One install. Full design system. Global CSS, design tokens, 6 themes, 150+ icons, 25 components, interactive JS, and TypeScript types — framework-agnostic, production-ready.
What is this?
@aravi1008/ui is a CSS-class-based design system — similar to Bootstrap, but modern:
- Design tokens — CSS custom properties, SCSS vars, and JS exports for consistent spacing, colour, and type scale
- 6 built-in themes — switchable at runtime via a data attribute or JS API, no page reload
- 150+ SVG icons — single sprite, inline or
<use>, any size - 25 CSS components — buttons, cards, modals, drawers, toasts, accordions, tables, and more
- Zero-dependency JS API — wire interactive components with one call:
initAll() - Full TypeScript types — for every public function and interface
- Works everywhere — React, Vue, Angular, Svelte, Next.js, Nuxt, plain HTML, HTMX
Install
npm install @aravi1008/uiQuick Start — 5 minutes to a full design system
1. Import the CSS
// In your app entry — main.js, _app.tsx, main.ts, etc.
import '@aravi1008/ui/css';Or via SCSS / LESS:
/* SCSS */
@use '@aravi1008/ui/scss';/* LESS */
@import '@aravi1008/ui/less';2. Set a theme (optional — defaults to light)
<!-- On your root element -->
<html data-av-theme="light">// Or via JS — reads OS dark mode pref automatically
import { initTheme } from '@aravi1008/ui';
initTheme('light');3. Start using components
<button class="av-btn av-btn-primary">Get Started</button>
<span class="av-badge av-badge-success">New</span>
<div class="av-alert av-alert-info">Your changes were saved.</div>4. Wire interactive components (once)
import { initAll } from '@aravi1008/ui/components';
initAll(); // wires all modals, drawers, dropdowns, accordions, tabsThat's it. Consistent design system across your entire project in 4 steps.
Why use this instead of plain CSS?
| Problem | @aravi1008/ui solution |
|---------|--------------------------|
| Spacing is inconsistent across the codebase | Token scale — var(--av-spacing-4) everywhere, same values |
| Adding dark mode is a week of work | Built in. data-av-theme="dark" on <html> — done |
| Rebuilding buttons and modals every project | 25 pre-built components, one class name away |
| No icon system | 150 SVG icons in one sprite — works like a font |
| Framework lock-in | Pure CSS — same classes in React, Vue, Angular, plain HTML |
| Accessibility gaps | :focus-visible, prefers-reduced-motion, ARIA on all interactive components |
| TypeScript types for the JS API | Ships with full .d.ts — autocomplete in every IDE |
Framework Integration
React / Next.js
// app/layout.tsx or pages/_app.tsx
import '@aravi1008/ui/css';
// Use classes directly on JSX
export default function Page() {
return (
<div className="av-container av-py-8">
<h1 className="av-text-3xl av-font-bold av-mb-4">Hello</h1>
<button className="av-btn av-btn-primary av-btn-lg">Get started</button>
</div>
);
}// Wire interactive components once — supports SPA re-renders
'use client';
import { useEffect } from 'react';
import { initAll } from '@aravi1008/ui/components';
export function Providers({ children }: { children: React.ReactNode }) {
useEffect(() => {
const cleanup = initAll({ observe: true }); // MutationObserver for dynamic content
return cleanup;
}, []);
return <>{children}</>;
}Vue 3 / Nuxt
// main.ts or plugins/ui.ts
import '@aravi1008/ui/css';
import { initAll } from '@aravi1008/ui/components';
app.mount('#app');
initAll({ observe: true });<template>
<div class="av-card av-p-6">
<h2 class="av-text-xl av-font-semibold av-mb-3">Card Title</h2>
<button class="av-btn av-btn-primary" @click="save">Save</button>
</div>
</template>Angular
// angular.json — add to styles array:
"node_modules/@aravi1008/ui/dist/index.css"// app.component.ts
import { initAll } from '@aravi1008/ui/components';
@Component({ ... })
export class AppComponent implements OnInit {
ngOnInit() { initAll(); }
}<!-- template -->
<button class="av-btn av-btn-primary">Submit</button>Svelte / SvelteKit
// src/app.css or +layout.ts
import '@aravi1008/ui/css';<script>
import { onMount, onDestroy } from 'svelte';
import { initAll } from '@aravi1008/ui/components';
let cleanup;
onMount(() => { cleanup = initAll({ observe: true }); });
onDestroy(() => cleanup?.());
</script>
<button class="av-btn av-btn-primary">Click</button>Plain HTML / CDN
<link rel="stylesheet" href="https://unpkg.com/@aravi1008/ui/dist/index.css">
<script type="module">
import { initAll, toast } from 'https://unpkg.com/@aravi1008/ui/dist/components.js';
initAll();
document.querySelector('#btn').onclick = () =>
toast.show({ title: 'Hello!', type: 'success' });
</script>Theming
Six built-in themes — switch at any time with no flash:
<html data-av-theme="light"> <!-- default, clean white -->
<html data-av-theme="dark"> <!-- deep charcoal, auto via OS pref -->
<html data-av-theme="forest"> <!-- earthy greens -->
<html data-av-theme="ocean"> <!-- blue-teal -->
<html data-av-theme="professional"> <!-- corporate blue -->
<html data-av-theme="corporate"> <!-- navy + gold -->JS API:
import { setTheme, getTheme, initTheme } from '@aravi1008/ui';
initTheme('light'); // init — reads localStorage, falls back to OS pref
setTheme('dark'); // switch
setTheme('forest', { persist: true }); // switch + save to localStorage
getTheme(); // → 'forest'Scoped themes (different theme per section):
<div data-av-theme="dark" class="av-p-6">
<!-- Everything inside uses the dark theme, rest of page unchanged -->
<button class="av-btn av-btn-primary">Dark Button</button>
</div>Components
Buttons
<button class="av-btn av-btn-primary">Primary</button>
<button class="av-btn av-btn-secondary">Secondary</button>
<button class="av-btn av-btn-outline">Outline</button>
<button class="av-btn av-btn-ghost">Ghost</button>
<button class="av-btn av-btn-danger">Danger</button>
<button class="av-btn av-btn-success">Success</button>
<!-- Sizes: xs / sm / (default) / lg / xl -->
<button class="av-btn av-btn-primary av-btn-xs">XS</button>
<button class="av-btn av-btn-primary av-btn-lg">Large</button>
<!-- States -->
<button class="av-btn av-btn-primary av-btn-loading">Saving…</button>
<button class="av-btn av-btn-primary av-btn-block">Full Width</button>Modal
<button class="av-btn av-btn-primary" data-av-modal-open="#confirm">Open</button>
<div class="av-modal-backdrop" id="confirm">
<div class="av-modal av-modal-md">
<div class="av-modal-header">
<h2 class="av-modal-title">Confirm</h2>
<button class="av-modal-close" data-av-modal-close aria-label="Close">×</button>
</div>
<div class="av-modal-body"><p>Are you sure?</p></div>
<div class="av-modal-footer">
<button class="av-btn av-btn-outline" data-av-modal-close>Cancel</button>
<button class="av-btn av-btn-danger">Delete</button>
</div>
</div>
</div>import { modal } from '@aravi1008/ui/components';
modal.open('#confirm');
modal.close('#confirm');Toast
import { toast } from '@aravi1008/ui/components';
toast.show({ title: 'Saved!', type: 'success' });
toast.show({ title: 'Error', description: 'Please try again.', type: 'error', duration: 0 });
toast.configure({ maxVisible: 3 }); // queue excess toasts, show when space clearsDrawer
<button data-av-drawer-open="#sidebar">Open Sidebar</button>
<div class="av-drawer-backdrop" id="sidebar">
<aside class="av-drawer av-drawer-left av-drawer-md">
<div class="av-drawer-header">
<h2>Menu</h2>
<button data-av-drawer-close aria-label="Close">×</button>
</div>
<div class="av-drawer-body"><nav>...</nav></div>
</aside>
</div>Dropdown
<div class="av-dropdown">
<button class="av-dropdown-trigger av-btn av-btn-outline">Options ▾</button>
<ul class="av-dropdown-menu">
<li class="av-dropdown-item" tabindex="0">Edit</li>
<li class="av-dropdown-item" tabindex="0">Duplicate</li>
<li class="av-dropdown-divider"></li>
<li class="av-dropdown-item av-dropdown-item-danger" tabindex="0">Delete</li>
</ul>
</div>Accordion
<div class="av-accordion">
<div class="av-accordion-item">
<button class="av-accordion-trigger" aria-expanded="false" aria-controls="a1">
Question
<svg class="av-accordion-icon av-icon"><use href="#chevron-down"/></svg>
</button>
<div id="a1" class="av-accordion-content">
<div class="av-accordion-body">Answer goes here.</div>
</div>
</div>
</div>Alert / Badge / Card
<!-- Alerts -->
<div class="av-alert av-alert-info">Informational message.</div>
<div class="av-alert av-alert-success">Completed successfully.</div>
<div class="av-alert av-alert-warning">Review before continuing.</div>
<div class="av-alert av-alert-error">Something went wrong.</div>
<!-- Badges -->
<span class="av-badge av-badge-primary">Beta</span>
<span class="av-badge av-badge-success">Active</span>
<span class="av-badge av-badge-warning">Pending</span>
<!-- Card -->
<div class="av-card av-card-shadow av-card-hover">
<div class="av-card-header">
<h3 class="av-card-title">Card</h3>
</div>
<div class="av-card-body"><p>Content</p></div>
<div class="av-card-footer">
<button class="av-btn av-btn-primary av-btn-sm">View</button>
</div>
</div>Other Components
| Component | Class prefix |
|-----------|-------------|
| Table | av-table, av-table-striped, av-table-hover |
| Tabs | av-tabs, av-tab, av-tab-active, av-tab-panel |
| Progress bar | av-progress, av-progress-bar |
| Skeleton loader | av-skeleton, av-skeleton-text, av-skeleton-circle |
| Switch / Toggle | av-switch, av-switch-checked |
| Avatar | av-avatar, av-avatar-group, av-avatar-sm/lg |
| Breadcrumb | av-breadcrumb, av-breadcrumb-item |
| Pagination | av-pagination, av-page-item, av-page-active |
| Tooltip | av-tooltip (data-av-tooltip attribute) |
| Spinner | av-spinner, av-spinner-primary/sm/lg |
| Stat / KPI | av-stat, av-stat-value, av-stat-label |
| Stepper | av-stepper, av-step, av-step-active/complete |
| Timeline | av-timeline, av-timeline-item |
| Navbar | av-navbar, av-navbar-toggle, av-navbar-collapse |
Data Table
import { createTable } from '@aravi1008/ui/components';
const ctrl = createTable('#my-table', {
columns: [
{ key: 'name', label: 'Name', sortable: true },
{ key: 'role', label: 'Role' },
{ key: 'status', label: 'Status',
render: (v) => `<span class="av-badge av-badge-${v === 'active' ? 'success' : 'error'}">${v}</span>` },
],
rows: [
{ name: 'Alice', role: 'Engineer', status: 'active' },
{ name: 'Bob', role: 'Designer', status: 'inactive' },
],
pagination: { enabled: true, rowsPerPage: 10 },
striped: true,
hoverable: true,
});
// Programmatic updates
ctrl.setRows(newData);
ctrl.setLoading(true);
ctrl.sort('name', 'asc');
ctrl.setPage(2);
ctrl.destroy();Utilities
<!-- Spacing -->
<div class="av-p-4 av-m-2 av-mx-auto av-gap-6">...</div>
<!-- Flex -->
<div class="av-flex av-items-center av-justify-between av-gap-4">...</div>
<!-- Grid -->
<div class="av-grid av-grid-cols-3 av-gap-6">...</div>
<!-- Typography -->
<h1 class="av-text-4xl av-font-bold av-tracking-tight">Heading</h1>
<p class="av-text-sm av-text-secondary av-leading-relaxed">Body</p>
<!-- Colours -->
<div class="av-bg-primary av-text-white av-p-4">Branded</div>
<!-- Animation -->
<div class="av-animate-spin">Spinner</div>
<div class="av-animate-pulse">Pulsing skeleton</div>
<!-- Aspect ratio -->
<div class="av-aspect-video"><iframe ...></iframe></div>
<!-- Scroll -->
<div class="av-scroll-y av-scroll-smooth">Scrollable panel</div>
<!-- Responsive -->
<div class="av-hidden av-md:block">Desktop only</div>
<!-- Print -->
<div class="av-print:hidden">Hidden when printing</div>Design Tokens
Use the CSS custom properties in your own styles — they update automatically when the theme changes:
.my-hero {
background: var(--av-theme-color-primary);
color: var(--av-theme-color-on-primary);
padding: var(--av-spacing-8) var(--av-spacing-12);
border-radius: var(--av-radius-xl);
font-size: var(--av-text-xl);
}
.my-card {
background: var(--av-theme-color-surface);
border: 1px solid var(--av-theme-color-border);
box-shadow: var(--av-shadow-md);
}Import tokens in JS:
import tokens from '@aravi1008/ui/tokens';
console.log(tokens.color.primary); // → '#2563eb'Icons
<!-- Via sprite (recommended) -->
<svg class="av-icon"><use href="/node_modules/@aravi1008/ui/dist/icons/sprite.svg#arrow-right"/></svg>
<!-- Sizes -->
<svg class="av-icon av-icon-sm">...</svg> <!-- 16px -->
<svg class="av-icon">...</svg> <!-- 20px (default) -->
<svg class="av-icon av-icon-lg">...</svg> <!-- 24px -->
<svg class="av-icon av-icon-xl">...</svg> <!-- 32px -->150+ icons: arrows, UI controls, social, file types, data viz, and more.
Package Exports
| Import | What you get |
|--------|-------------|
| @aravi1008/ui/css | Full compiled CSS |
| @aravi1008/ui/css/min | Minified CSS |
| @aravi1008/ui/scss | SCSS source (customisable) |
| @aravi1008/ui/less | LESS source |
| @aravi1008/ui | JS theme switcher (setTheme, initTheme, getTheme) |
| @aravi1008/ui/components | JS interactive components |
| @aravi1008/ui/tokens | Token JS object |
| @aravi1008/ui/tokens/css | Token CSS vars only |
| @aravi1008/ui/tokens/scss | Token SCSS vars only |
| @aravi1008/ui/themes/* | Individual theme CSS |
| @aravi1008/ui/icons | SVG sprite path |
| @aravi1008/ui/icons/* | Individual SVG files |
TypeScript
import type { ToastOptions, TableColumn, TableController, InitAllOptions } from '@aravi1008/ui/components';
import type { ThemeName } from '@aravi1008/ui';
const col: TableColumn = {
key: 'status',
label: 'Status',
render: (v) => `<span class="av-badge av-badge-success">${v}</span>`,
};
const opts: ToastOptions = { title: 'Done', type: 'success', placement: 'top-right' };Upgrading Existing Projects
From Bootstrap
| Bootstrap | @aravi1008/ui |
|-----------|----------------|
| btn btn-primary | av-btn av-btn-primary |
| d-flex align-items-center | av-flex av-items-center |
| card | av-card |
| alert alert-success | av-alert av-alert-success |
| badge bg-success | av-badge av-badge-success |
| modal fade | av-modal-backdrop + initAll() |
| container | av-container |
| --bs-* vars | var(--av-theme-color-*) |
From Tailwind
The utility naming follows a similar pattern — av-flex, av-p-4, av-text-lg — so the mental model transfers. No PurgeCSS needed (bundle is already ~240KB).
Migration approach
# 1. Install
npm install @aravi1008/ui
# 2. Add one import to your entry point
# import '@aravi1008/ui/css';
# 3. Both systems coexist — migrate file by file
# Before: <div class="card">
# After: <div class="av-card">
# 4. Remove old library once fully migrated
npm uninstall bootstrapEcosystem
| Package | Purpose | Status |
|---------|---------|--------|
| @aravi1008/ui | Core — CSS, tokens, vanilla JS | ✅ Published |
| @aravi1008/ui-mat | JSX components for React/Vue/Angular/Svelte | 🚧 Coming soon |
| Docs site | Full component reference + live demos | 🚧 Coming soon |
@aravi1008/ui-matbrings MUI-style JSX components —<Button>,<Alert>,<Modal>,<Drawer>— that map to the sameav-CSS classes under the hood. Same design system, multiple consumption patterns.
Browser Support
Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
License
MIT © Aravindhan Sivaraman
