npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@underverse-ui/underverse

v1.0.113

Published

Underverse UI – reusable React/Next.js UI components

Readme

Underverse UI

Docs: https://underverse.infiniq.com.vn/vi/docs/underverse

Author: Tran Van Bach

A comprehensive UI component library for React/Next.js applications, extracted from the main project. Built with Tailwind CSS, clsx, and tailwind-merge.

✨ Features

  • 🎨 60+ UI Components - Buttons, Modals, DatePicker, DataTable, and more
  • 🌐 Multi-language Support - Built-in translations for English, Vietnamese, Korean, Japanese
  • Tree-shakeable - Import only what you need
  • 🔌 Flexible i18n - Works with next-intl or standalone React
  • 🎯 TypeScript First - Full type definitions included
  • 🌙 Dark Mode Ready - Supports light/dark themes via CSS variables

Supported Locales

| Locale | Language | Flag | | ------ | ---------- | ---- | | en | English | 🇺🇸 | | vi | Tiếng Việt | 🇻🇳 | | ko | 한국어 | 🇰🇷 | | ja | 日本語 | 🇯🇵 |

Requirements

  • Node >= 18
  • Peer dependencies: react, react-dom
  • Optional: next, next-intl (for Next.js projects)

Agent-Readable Metadata

For coding agents and automation tools:

  • AGENTS.md: concise usage and integration rules.
  • api-reference.json: generated export index from src/index.ts.
  • llms.txt: compact LLM-friendly quickstart.
  • agent-recipes.json: structured setup/use recipes.

Regenerate API metadata:

npm run generate:api

Installation

# Install the package
npm i @underverse-ui/underverse

# For Next.js projects (with next-intl)
npm i react react-dom next next-intl

# For standalone React projects (Vite, CRA, etc.)
npm i react react-dom

Tailwind CSS Configuration

Components use color variables like primary, secondary, destructive, etc. Make sure your Tailwind theme/tokens include these variables.


⚡ Performance Optimization

Optimize Package Imports (Next.js)

For best performance, add optimizePackageImports to your Next.js config:

// next.config.js
module.exports = {
  experimental: {
    optimizePackageImports: ["lucide-react", "@underverse-ui/underverse"],
  },
};

This provides:

  • ✅ 15-70% faster dev boot
  • ✅ 28% faster builds
  • ✅ 40% faster cold starts
  • ✅ Automatic tree-shaking for barrel imports

Dynamic Imports for Heavy Components

For pages that conditionally show DataTable or DatePicker:

import dynamic from "next/dynamic";

const DataTable = dynamic(() => import("@underverse-ui/underverse").then((m) => m.DataTable), { ssr: false, loading: () => <Skeleton /> });

Web Interface Guidelines Compliant

All components follow Vercel Web Interface Guidelines:

  • focus-visible ring (not :focus)
  • ✅ Label htmlFor attribute
  • ✅ ARIA attributes for accessibility
  • overscroll-behavior: contain for modals
  • ✅ Proper ellipsis () typography
  • ✅ Locale-aware date formatting with Intl.DateTimeFormat

Package Exports

Hiện tại package publish một public entry point duy nhất:

import {
  Button,
  DataTable,
  Form,
  FormField,
  UEditor,
} from "@underverse-ui/underverse";

Không có subpath export như @underverse-ui/underverse/form ở version hiện tại.

Lưu ý:

  • Nhiều component trong package là client component và nên dùng trong môi trường React client.
  • Form primitives yêu cầu react-hook-form@hookform/resolvers.
  • UEditor và các component dựa trên Tiptap yêu cầu peer dependencies tương ứng.

�🚀 Quick Start

Overlay Scrollbars (Optional, Recommended)

Underverse now uses opt-in, component-level OverlayScrollbars. There is no global DOM scanning, no default global mount, and no app-wide MutationObserver.

import "overlayscrollbars/overlayscrollbars.css";
import { OverlayScrollbarProvider, ScrollArea, DataTable } from "@underverse-ui/underverse";

function App() {
  return (
    <OverlayScrollbarProvider theme="os-theme-underverse" autoHide="leave">
      <ScrollArea className="h-56 rounded-xl border border-border" useOverlayScrollbar>
        {/* long content */}
      </ScrollArea>

      <DataTable
        columns={columns}
        data={rows}
        useOverlayScrollbar
      />
    </OverlayScrollbarProvider>
  );
}

Provider behavior:

  • Provider is configuration only (theme/options context).
  • Scrollbars initialize only on components explicitly enabled via useOverlayScrollbar.
  • Hard skip targets: html, body, [data-radix-portal], [role="dialog"], [aria-modal="true"], [data-sonner-toaster].
  • Per-node opt-out remains available via data-os-ignore.

Provider props:

  • enabled?: boolean
  • theme?: string
  • visibility?: "visible" | "hidden" | "auto"
  • autoHide?: "never" | "scroll" | "leave" | "move"
  • autoHideDelay?: number
  • dragScroll?: boolean
  • clickScroll?: boolean
  • exclude?: string default: html, body, [data-os-ignore], [data-radix-portal], [role='dialog'], [aria-modal='true'], [data-sonner-toaster]
  • selector?: string (deprecated, ignored; kept for backward compatibility)

Component-level enable flags:

  • ScrollArea: useOverlayScrollbar?: boolean (default false)
    • className styles the outer wrapper
    • contentClassName styles the scroll viewport
    • set border/radius explicitly; the primitive no longer hardcodes rounded corners
  • Table: useOverlayScrollbar?: boolean (default false)
  • DataTable: useOverlayScrollbar?: boolean (default false)
  • Combobox: useOverlayScrollbar?: boolean (default false)
  • MultiCombobox: useOverlayScrollbar?: boolean (default false)
  • CategoryTreeSelect: useOverlayScrollbar?: boolean (default false)
  • Textarea: useOverlayScrollbar?: boolean (default false)
  • OverlayScrollArea: dedicated wrapper for heavy scroll zones (enabled default true)

When to use:

  • Long virtualized/table/list panels
  • Fixed-height navigation panels and log viewers

When not to use:

  • Normal form fields
  • Short modal/dialog content
  • Full page root scrolling

Standalone React (Vite, CRA, etc.)

import { TranslationProvider, Button, DatePicker, ToastProvider, useToast } from "@underverse-ui/underverse";

function App() {
  return (
    <TranslationProvider locale="vi">
      <ToastProvider>
        <MyComponent />
      </ToastProvider>
    </TranslationProvider>
  );
}

function MyComponent() {
  const { addToast } = useToast();

  return (
    <div>
      <DatePicker onChange={(date) => console.log(date)} />
      <Button onClick={() => addToast({ type: "success", message: "Hello!" })}>Click me</Button>
    </div>
  );
}

Next.js (with next-intl)

import { Button, ToastProvider, useToast } from "@underverse-ui/underverse";

function App() {
  const { addToast } = useToast();
  return (
    <ToastProvider>
      <Button onClick={() => addToast({ type: "success", message: "Hello" })}>Click me</Button>
    </ToastProvider>
  );
}

Exported Components

Core Components

  • Buttons: Button
  • Display: Badge, Card, Avatar, Skeleton, Progress
  • Form Inputs: Input, Textarea, Checkbox, Switch, Label

Feedback & Overlays

  • Modal, ToastProvider, useToast, Tooltip, Popover, Sheet (includes Drawer, SlideOver, BottomSheet, SidebarSheet), Alert, GlobalLoading (includes PageLoading, InlineLoading, ButtonLoading)

Form Controls & Pickers

  • RadioGroup, Slider, DatePicker, Combobox, MultiCombobox, CategoryTreeSelect

Navigation & Structure

  • Breadcrumb, Tabs (includes SimpleTabs, PillTabs, VerticalTabs), DropdownMenu, Pagination, Section, ScrollArea, OverlayScrollArea

Data Display

  • Table, DataTable

Media Components

  • SmartImage, ImageUpload, Carousel, UEditor

Utilities

  • ClientOnly, Loading, NotificationModal, AccessDenied, OverlayControls
  • Headless controls: ThemeToggle, LanguageSwitcher
  • Utility functions: cn, DateUtils, style constants

Important Notes

  • Library is i18n‑agnostic: components have sensible English defaults and accept text via props.
  • If your app uses next-intl, you can merge our ready‑made messages to localize built‑in texts.
  • NotificationBell is not exported (depends on project-specific API/socket implementations).
  • FloatingContacts remains app-only and is not exported from the package.

📦 Date Utilities

The package includes standalone date utilities with locale support (no Next.js required):

import { DateUtils } from "@underverse-ui/underverse";

// Format dates with locale
DateUtils.formatDate(new Date(), "ko"); // "2026년 1월 5일"
DateUtils.formatDate(new Date(), "ja"); // "2026年1月5日"
DateUtils.formatDate(new Date(), "vi"); // "05/01/2026"
DateUtils.formatDate(new Date(), "en"); // "January 5, 2026"

// Relative time formatting
DateUtils.formatTimeAgo(new Date(Date.now() - 3600000), "ko"); // "1시간 전"
DateUtils.formatTimeAgo(new Date(Date.now() - 3600000), "ja"); // "1時間前"

// Smart date formatting (Today, Yesterday, or full date)
DateUtils.formatDateSmart(new Date(), "ja"); // "今日 14:30"

// Utility checks
DateUtils.isToday(new Date()); // true
DateUtils.isYesterday(new Date(Date.now() - 86400000)); // true

// Get day of week
DateUtils.getDayOfWeek(new Date(), "ko"); // "일요일"
DateUtils.getDayOfWeek(new Date(), "ja"); // "日曜日"

// Form input formatting
DateUtils.formatDateForInput(new Date()); // "2026-01-05"
DateUtils.formatDateTimeForInput(new Date()); // "2026-01-05T14:30"

Available Date Functions

| Function | Description | | ------------------------------- | ----------------------------------- | | formatDate(date, locale) | Full date format | | formatDateShort(date, locale) | Short date format | | formatTime(date, locale) | Time only (HH:mm) | | formatDateTime(date, locale) | Date + time | | formatTimeAgo(date, locale) | Relative time (e.g., "2 hours ago") | | formatDateSmart(date, locale) | Today/Yesterday/Full date | | isToday(date) | Check if date is today | | isYesterday(date) | Check if date is yesterday | | getDayOfWeek(date, locale) | Get localized day name | | formatDateForInput(date) | YYYY-MM-DD format | | formatDateTimeForInput(date) | YYYY-MM-DDTHH:mm format |


🎨 Animation Utilities

The package includes ShadCN-compatible animation utilities:

import { useShadCNAnimations, injectAnimationStyles, getAnimationStyles } from "@underverse-ui/underverse";

// React hook - automatically injects styles on mount
function MyComponent() {
  useShadCNAnimations();
  return <div className="animate-accordion-down">Content</div>;
}

// Manual injection (for non-React usage)
injectAnimationStyles();

// Get CSS string for custom injection
const cssString = getAnimationStyles();

Available Animations

| Class | Description | | ------------------------------ | ---------------------------- | | animate-accordion-down | Accordion expand animation | | animate-accordion-up | Accordion collapse animation | | animate-caret-blink | Blinking caret cursor | | animate-fade-in | Fade in effect | | animate-fade-out | Fade out effect | | animate-slide-in-from-top | Slide in from top | | animate-slide-in-from-bottom | Slide in from bottom | | animate-slide-in-from-left | Slide in from left | | animate-slide-in-from-right | Slide in from right | | animate-zoom-in | Zoom in effect | | animate-zoom-out | Zoom out effect |


next-intl Integration (Next.js App Router)

  1. Configure plugin and time zone (to avoid ENVIRONMENT_FALLBACK):
// next.config.ts
import createNextIntlPlugin from "next-intl/plugin";

const withNextIntl = createNextIntlPlugin({
  locales: ["vi", "en"],
  defaultLocale: "vi",
  timeZone: "Asia/Ho_Chi_Minh", // important for SSR
});

export default withNextIntl({
  // your other Next config
});
  1. Merge underverse messages with your app messages:
// app/layout.tsx (simplified)
import { NextIntlClientProvider, getMessages } from "next-intl/server";
import { underverseMessages } from "@underverse-ui/underverse";

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const appMessages = await getMessages();
  const locale = "vi"; // derive from params/headers
  const uv = underverseMessages[locale] || underverseMessages.en;
  const messages = { ...uv, ...appMessages }; // app overrides uv if overlaps

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}
  1. Use components normally. Any built‑in texts (DatePicker/Pagination/DataTable/Alert/ImageUpload…) will use merged messages. You can still override labels via props if desired.

🌐 TranslationProvider API

For standalone React apps (without next-intl):

import { TranslationProvider } from "@underverse-ui/underverse";

function App() {
  return (
    <TranslationProvider
      locale="ko" // "en" | "vi" | "ko" | "ja"
      translations={{
        // Optional: override default translations
        Common: {
          close: "닫기 (custom)",
        },
      }}
    >
      {children}
    </TranslationProvider>
  );
}

TranslationProvider Props

| Prop | Type | Default | Description | | -------------- | ------------------------------ | ----------- | ---------------------------- | | locale | "en" \| "vi" \| "ko" \| "ja" | "en" | Active locale | | translations | Translations | undefined | Custom translation overrides | | children | ReactNode | - | Child components |


Message Keys Summary

  • Common: close, closeAlert, notifications, newNotification, readStatus, openLink, theme, lightTheme, darkTheme, systemTheme, density, compact, normal, comfortable, columns
  • ValidationInput: required, typeMismatch, pattern, tooShort, tooLong, rangeUnderflow, rangeOverflow, stepMismatch, badInput, invalid
  • Loading: loadingPage, pleaseWait
  • DatePicker: placeholder, today, clear
  • Pagination: navigationLabel, showingResults ({startItem},{endItem},{totalItems}), firstPage, previousPage, previous, nextPage, next, lastPage, pageNumber ({page}), itemsPerPage, search, noOptions
  • OCR.imageUpload: dragDropText, browseFiles, supportedFormats

📋 Exported Components

Core Components

  • Buttons: Button
  • Display: Badge, Card, Avatar, Skeleton, Progress
  • Form Inputs: Input, PasswordInput, NumberInput, SearchInput, Textarea, Checkbox, Switch, Label, TagInput

Feedback & Overlays

  • Modal, ToastProvider, useToast, Tooltip, Popover
  • Sheet (includes Drawer, SlideOver, BottomSheet, SidebarSheet)
  • Alert, GlobalLoading (includes PageLoading, InlineLoading, ButtonLoading)

Form Controls & Pickers

  • RadioGroup, Slider, DatePicker, DateRangePicker, TimePicker, Calendar
  • Combobox, MultiCombobox, CategoryTreeSelect, ColorPicker

Navigation & Structure

  • Breadcrumb, Tabs (includes SimpleTabs, PillTabs, VerticalTabs)
  • DropdownMenu, Pagination, SimplePagination, CompactPagination
  • Section, ScrollArea

Data Display

  • Table, DataTable, List, Grid, Timeline

Media Components

  • SmartImage, ImageUpload, Carousel, FallingIcons, Watermark, UEditor

Utilities

  • ClientOnly, Loading, NotificationModal, AccessDenied, OverlayControls
  • ThemeToggle, LanguageSwitcher (headless)
  • cn, DateUtils, useShadCNAnimations

License

MIT

Author

Tran Van Bach


Headless Components Usage

These variants avoid app-specific contexts and routing so you can wire them to your own state.

ThemeToggle (headless)

import { ThemeToggle } from "@underverse-ui/underverse";
import type { ThemeToggleProps, ThemeMode } from "@underverse-ui/underverse";
import { useState } from "react";

export default function ExampleThemeToggle() {
  const [theme, setTheme] = useState<ThemeMode>("system");
  return (
    <ThemeToggle
      theme={theme}
      onChange={setTheme}
      // optional labels
      labels={{ heading: "Theme", light: "Light", dark: "Dark", system: "System" }}
    />
  );
}

If you use next-themes or a custom context, pass your current theme and the setter to onChange.

LanguageSwitcher (headless)

import { LanguageSwitcher } from "@underverse-ui/underverse";
import type { LanguageOption } from "@underverse-ui/underverse";
import { useRouter, usePathname } from "next/navigation";

const locales: LanguageOption[] = [
  { code: "vi", name: "Tiếng Việt", flag: "🇻🇳" },
  { code: "en", name: "English", flag: "🇺🇸" },
  { code: "ko", name: "한국어", flag: "🇰🇷" },
  { code: "ja", name: "日本語", flag: "🇯🇵" },
];

export default function ExampleLanguageSwitcher({ currentLocale }: { currentLocale: string }) {
  const router = useRouter();
  const pathname = usePathname();

  const onSwitch = (code: string) => {
    // Replace first segment as locale, e.g. /vi/... -> /en/...
    const segs = pathname.split("/");
    segs[1] = code;
    router.push(segs.join("/"));
  };

  return <LanguageSwitcher locales={locales} currentLocale={currentLocale} onSwitch={onSwitch} labels={{ heading: "Language" }} />;
}

📁 Full Export Reference

// Core Components
import {
  Button,
  Badge,
  Card,
  Avatar,
  Skeleton,
  Progress,
  Input,
  PasswordInput,
  NumberInput,
  SearchInput,
  Textarea,
  Checkbox,
  Switch,
  Label,
  TagInput,
} from "@underverse-ui/underverse";

// Overlays
import {
  Modal,
  ToastProvider,
  useToast,
  Tooltip,
  Popover,
  Sheet,
  Drawer,
  SlideOver,
  BottomSheet,
  SidebarSheet,
  Alert,
  GlobalLoading,
  PageLoading,
  InlineLoading,
  ButtonLoading,
} from "@underverse-ui/underverse";

// Pickers
import {
  DatePicker,
  DateRangePicker,
  TimePicker,
  Calendar,
  Combobox,
  MultiCombobox,
  CategoryTreeSelect,
  ColorPicker,
  RadioGroup,
  Slider,
} from "@underverse-ui/underverse";

// Navigation
import {
  Breadcrumb,
  Tabs,
  SimpleTabs,
  PillTabs,
  VerticalTabs,
  DropdownMenu,
  Pagination,
  SimplePagination,
  CompactPagination,
  Section,
  ScrollArea,
} from "@underverse-ui/underverse";

// Data Display
import { Table, DataTable, List, Grid, Timeline, Watermark } from "@underverse-ui/underverse";

// Media
import { SmartImage, ImageUpload, Carousel, FallingIcons, UEditor } from "@underverse-ui/underverse";

// Utilities
import {
  cn,
  DateUtils,
  useShadCNAnimations,
  injectAnimationStyles,
  ClientOnly,
  Loading,
  NotificationModal,
  AccessDenied,
  ThemeToggle,
  LanguageSwitcher,
} from "@underverse-ui/underverse";

// i18n
import {
  TranslationProvider,
  useUnderverseTranslations,
  useUnderverseLocale,
  underverseMessages,
  getUnderverseMessages,
} from "@underverse-ui/underverse";

// Types
import type {
  ButtonProps,
  InputProps,
  DatePickerProps,
  ComboboxProps,
  PaginationProps,
  DataTableColumn,
  Locale,
  Translations,
} from "@underverse-ui/underverse";

🧪 Testing

Test with React (Vite)

# Create new Vite project
npm create vite@latest my-test-app -- --template react-ts
cd my-test-app

# Install underverse
npm i @underverse-ui/underverse

# Add Tailwind CSS
npm i -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Test with Next.js

# Create new Next.js project
npx create-next-app@latest my-test-app --typescript --tailwind
cd my-test-app

# Install underverse
npm i @underverse-ui/underverse next-intl