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

@mdigitalcn/uikit

v1.0.13

Published

Modern React component library built with Tailwind CSS v4

Readme

@mdigitalcn/uikit

Production-grade React component library built on Tailwind CSS v4.

76+ components · Slot-based color system · Tree-shakeable · 1,500+ tests · Full TypeScript

npm downloads bundle size license

Changelog · Report a Bug · Request a Feature


Table of Contents


Why This Library

| | @mdigitalcn/uikit | shadcn/ui | Ant Design | Chakra UI | |---|---|---|---|---| | Theming | Slot-based — 1 prop themes everything | Copy-paste, manual | Token config object | colorScheme prop | | Tailwind | v4 native (@theme, @source) | v3 with config | No Tailwind | No Tailwind | | Bundle | Tree-shakeable per component | Copy into project | 300 KB+ bundle | 200 KB+ bundle | | Dark mode | Zero-config CSS variables | Manual dark: classes | ConfigProvider | ColorModeProvider | | Components | 76+ production-ready | ~50 primitives | 60+ | 60+ | | Tests | 1,500+ | Community | Extensive | Moderate |

What makes it different

Single color prop. color="primary" sets one CSS variable (--_c). Every internal surface — background, border, text, hover, focus ring — derives from it automatically. Changing a theme color propagates to every component at once. No per-component variant tables to maintain.

Tailwind CSS v4 native. Not "compatible with Tailwind" — built on v4's architecture. @theme defines all tokens as CSS custom properties. @source auto-scans compiled output. @custom-variant dark drives dark mode via a single class. @utility exposes the slot system as real Tailwind utilities.

Five variables control everything. The entire visual system cascades from five overrides:

:root {
  --brand:   oklch(55% 0.22 260); /* Primary brand hue */
  --accent:  oklch(55% 0.20 330); /* Accent hue */
  --radius:  0.375rem;            /* Base radius — all component radii derive from this */
  --scaling: 1;                   /* Size multiplier: 0.9 = compact, 1.1 = comfortable */
  --gray:    260;                 /* Neutral hue — hue-tinted grays */
}

Per-component entry points. Import exactly what you use. Bundlers tree-shake the rest:

import Button from '@mdigitalcn/uikit/button'
import Table  from '@mdigitalcn/uikit/table'

Quick Start

Install:

npm install @mdigitalcn/uikit

Peer dependencies: react >= 18, react-dom >= 18, lucide-react >= 0.400, tailwindcss >= 4.0

Add the stylesheet:

/* app.css or layout.css */
@import "tailwindcss";
@import "@mdigitalcn/uikit/styles/global.css";

One import. Tokens, dark mode, utilities, component styles — everything included.

Use components:

import Button   from '@mdigitalcn/uikit/button'
import Input    from '@mdigitalcn/uikit/input'
import Select   from '@mdigitalcn/uikit/select'

export default function App() {
  return (
    <div className="flex gap-3">
      <Input placeholder="Search..." />
      <Select options={[{ label: 'React', value: 'react' }]} />
      <Button color="primary">Go</Button>
    </div>
  )
}

Framework setup

// app/layout.tsx
import '@mdigitalcn/uikit/styles/global.css'
import { ThemeProvider } from '@mdigitalcn/uikit/theme'
import { getThemeScript } from '@mdigitalcn/uikit/theme'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        {/* Prevents dark-mode flash on SSR */}
        <script dangerouslySetInnerHTML={{ __html: getThemeScript() }} />
      </head>
      <body>
        <ThemeProvider defaultTheme="system">
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

All components are 'use client' — they can be imported directly in Server Components or wrapped in a client boundary.

// main.tsx
import '@mdigitalcn/uikit/styles/global.css'
import { ThemeProvider } from '@mdigitalcn/uikit/theme'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <ThemeProvider defaultTheme="system">
      <App />
    </ThemeProvider>
  </StrictMode>
)

The Slot Color System

The library's core idea: one color prop, zero per-component color variant tables.

When you write <Button color="primary">, it sets --_c to the primary color token. Every internal utility reads from --_c:

| Utility | Meaning | |---|---| | bg-slot | Background at full opacity | | bg-slot-10 | Background at 10% opacity (soft fills) | | bg-slot-20 | Background at 20% opacity | | text-slot | Foreground text in the slot color | | text-slot-fg | Text on top of bg-slot (meets contrast) | | border-slot | Border at full opacity | | border-slot-30 | Border at 30% opacity | | hover:bg-slot-90 | Hover state (10% darker) |

All eight semantic colors (primary, secondary, accent, success, error, warning, info, default) plus any custom color you define work automatically through this system:

<Button color="primary">Primary</Button>
<Button color="error">Delete</Button>
<Alert color="warning">Something needs attention</Alert>
<Badge color="success">Active</Badge>
<Input color="info" />

Status overrides color. When a form field carries a validation status the status color takes priority:

// error/warning/success/info replace the color prop on form components
<Input error="This field is required" />
<Select warning="Unusual value — double check" />
<Checkbox success />

Theming

Dark Mode

import { ThemeProvider, useTheme } from '@mdigitalcn/uikit/theme'

// Wrap your app once
<ThemeProvider defaultTheme="system">
  <App />
</ThemeProvider>

// Toggle anywhere
const { theme, setTheme } = useTheme()
setTheme('dark')    // 'light' | 'dark' | 'system'

All components use CSS custom properties. Dark mode swaps values at the :root level. Zero dark: prefixes appear in component code.

SSR Flash Prevention

import { getThemeScript } from '@mdigitalcn/uikit/theme'

// In your <head>, before any stylesheets
<script dangerouslySetInnerHTML={{ __html: getThemeScript() }} />

Reads localStorage and applies the correct class to <html> synchronously before React hydrates.


Theme Presets

Six curated presets ship out of the box. Import after global.css, then set data-theme on <html>:

@import "tailwindcss";
@import "@mdigitalcn/uikit/styles/global.css";

/* Pick one — or ship all and switch at runtime */
@import "@mdigitalcn/uikit/styles/themes/presets/corporate.css";
@import "@mdigitalcn/uikit/styles/themes/presets/vibrant.css";
@import "@mdigitalcn/uikit/styles/themes/presets/minimal.css";
@import "@mdigitalcn/uikit/styles/themes/presets/warm.css";
@import "@mdigitalcn/uikit/styles/themes/presets/ocean.css";
@import "@mdigitalcn/uikit/styles/themes/presets/rose.css";
<html data-theme="corporate" class="dark">

| Preset | Character | |---|---| | corporate | Muted blues, tight radius, professional | | vibrant | Saturated palette, bold accents | | minimal | Near-zero chroma, maximum whitespace | | warm | Amber and terracotta tones | | ocean | Teal and deep blue, calm | | rose | Pink-red brand, soft surfaces |

Presets compose freely with dark/light mode. data-theme="ocean" + class="dark" gives the dark ocean variant.


Custom Tokens

Override after the library import. Source order wins — no !important needed:

@import "tailwindcss";
@import "@mdigitalcn/uikit/styles/global.css";

:root {
  /* Brand */
  --color-primary:            oklch(55% 0.22 270);
  --color-primary-hover:      oklch(50% 0.24 270);
  --color-primary-active:     oklch(45% 0.26 270);
  --color-primary-foreground: oklch(99% 0 0);

  /* Shape */
  --radius: 0.5rem;      /* Cascades to --radius-button, --radius-card, etc. */
  --scaling: 1;          /* 0.9 = compact, 1.1 = comfortable */

  /* Typography */
  --font-sans: 'Inter', system-ui, sans-serif;

  /* Sizing overrides */
  --button-height-md: 2.5rem;
  --input-height-md:  2.5rem;
}

.dark {
  --color-primary:            oklch(65% 0.22 270);
  --color-background:         oklch(12% 0.01 270);
  --color-surface:            oklch(16% 0.01 270);
}

Common API

color

'default' | 'primary' | 'secondary' | 'accent' | 'success' | 'error' | 'warning' | 'info'

Available on every component that has visual state. Drives the slot color system.

size

'xs' | 'sm' | 'md' (default) | 'lg'

Controls height, padding, and font size. All values read from CSS token variables — override with --button-height-md, --input-height-sm, etc.

variant

Per-component options:

| Component | Variants | |---|---| | Button | solid outline soft dashed link ghost | | Input | outline filled | | Card | default solid outline soft ghost elevated | | Badge / Tag | default solid outline soft | | Checkbox / Toggle | solid outline soft | | Pagination | default solid soft | | Popover | default solid soft |

Validation state

All form components share the same validation API:

<Input   error="Required"              helperText="Min 8 characters" />
<Select  warning="Unusual value"       />
<Input   success                       helperText="Looks good" />
<Input   info="Auto-filled from SSO"   />

The status color replaces the color prop automatically.

Styling escape hatches

Three levels of override — use the lightest one that solves the problem:

// 1. Root element className
<Button className="shadow-lg w-full">Submit</Button>

// 2. Internal part classNames
<Input
  classNames={{
    root:   'mb-4',
    label:  'font-bold text-primary',
    input:  'tracking-wide font-mono',
  }}
/>

// 3. CSS selectors — every part has a semantic class and data-slot attribute
.select_option  { padding-inline: 1rem; }
[data-slot="trigger"] { min-width: 200px; }
[data-slot="popover-content"] { max-height: 400px; }

All Components

| Component | Import path | Notes | |---|---|---| | Input | @mdigitalcn/uikit/input | clearable, loading, icons, character count | | FloatInput | @mdigitalcn/uikit/float-input | floating label variant | | InputPassword | @mdigitalcn/uikit/input-password | show/hide toggle, strength indicator | | InputOTP | @mdigitalcn/uikit/input-otp | keyboard nav, paste, mask | | InputGroup | @mdigitalcn/uikit/input-group | addons, prefix, suffix | | NumberInput | @mdigitalcn/uikit/number-input | step, min/max, formatter | | Textarea | @mdigitalcn/uikit/textarea | auto-resize, character count | | Select | @mdigitalcn/uikit/select | virtualized, searchable, groups | | MultiSelect | @mdigitalcn/uikit/multi-select | chips, virtualized, searchable | | Cascader | @mdigitalcn/uikit/cascader | nested option tree, async load | | TreeSelect | @mdigitalcn/uikit/tree-select | hierarchical, checkboxes | | DatePicker | @mdigitalcn/uikit/date-picker | calendar, range, time picker | | ColorPicker | @mdigitalcn/uikit/color-picker | hex/rgb/hsl, presets | | Autocomplete | @mdigitalcn/uikit/autocomplete | async suggestions | | Mentions | @mdigitalcn/uikit/mentions | @-mention with dropdown | | Checkbox | @mdigitalcn/uikit/checkbox | indeterminate, solid/outline/soft | | CheckboxGroup | @mdigitalcn/uikit/checkbox-group | horizontal/vertical | | Radio | @mdigitalcn/uikit/radio | all variants | | RadioGroup | @mdigitalcn/uikit/radio-group | button style option | | Switch | @mdigitalcn/uikit/switch | loading state, label | | Slider | @mdigitalcn/uikit/slider | range, marks, tooltip | | Rating | @mdigitalcn/uikit/rating | half-star, custom icon | | Toggle | @mdigitalcn/uikit/toggle | | | ToggleGroup | @mdigitalcn/uikit/toggle-group | single/multiple | | Upload | @mdigitalcn/uikit/upload | drag-and-drop, list/picture | | Clipboard | @mdigitalcn/uikit/clipboard | copy-to-clipboard with feedback | | TagsInput | @mdigitalcn/uikit/tags-input | free-entry tags with delimiter |

| Component | Import path | Notes | |---|---|---| | Button | @mdigitalcn/uikit/button | 6 variants, ripple, loading, icon | | ButtonGroup | @mdigitalcn/uikit/button-group | connected borders, single selection | | Card | @mdigitalcn/uikit/card | 6 variants, clickable, cover image | | Grid | @mdigitalcn/uikit/grid | responsive column helper | | Divider | @mdigitalcn/uikit/divider | text label, orientation | | Collapse | @mdigitalcn/uikit/collapse | animated height | | Accordion | @mdigitalcn/uikit/accordion | single/multiple, flush | | ScrollArea | @mdigitalcn/uikit/scroll-area | custom styled scrollbar | | Resizable | @mdigitalcn/uikit/resizable | drag handle, panel groups |

| Component | Import path | Notes | |---|---|---| | Tabs | @mdigitalcn/uikit/tabs | 5 variants, lazy/destroy on hide | | Breadcrumbs | @mdigitalcn/uikit/breadcrumbs | separator, ellipsis collapse | | Pagination | @mdigitalcn/uikit/pagination | quick jumper, size changer, simple mode | | Stepper | @mdigitalcn/uikit/stepper | horizontal/vertical, status icons | | Dropdown | @mdigitalcn/uikit/dropdown | groups, icons, keyboard nav | | ContextMenu | @mdigitalcn/uikit/context-menu | right-click, nested menus | | Menubar | @mdigitalcn/uikit/menubar | app-style menu, keyboard nav | | NavigationMenu | @mdigitalcn/uikit/navigation-menu | horizontal/vertical, flyout panels | | Command | @mdigitalcn/uikit/command | palette, modal variant | | Anchor | @mdigitalcn/uikit/anchor | scroll-spy, nested, custom container | | Link | @mdigitalcn/uikit/link | underline variants | | FloatButton | @mdigitalcn/uikit/float-button | FAB, group, scroll-to-top |

| Component | Import path | Notes | |---|---|---| | Modal | @mdigitalcn/uikit/modal | composed + primitive API, sizes, color accents | | Drawer | @mdigitalcn/uikit/drawer | 4 directions, snap points, handle | | Tooltip | @mdigitalcn/uikit/tooltip | sides, align, arrow, delay | | Popover | @mdigitalcn/uikit/popover | 3 variants, portal control | | Tour | @mdigitalcn/uikit/tour | step-by-step product tours |

Overlay nesting — Select, Dropdown, DatePicker, Tooltip, and all other Popover-based components work correctly inside Modal and Drawer. See Overlay Nesting.

| Component | Import path | Notes | |---|---|---| | Notification | @mdigitalcn/uikit/notification | position, duration, queue | | Alert | @mdigitalcn/uikit/alert | closable, icon, actions | | Toast | @mdigitalcn/uikit/toast | imperative API, positions | | Progress | @mdigitalcn/uikit/progress | line/circle, striped, animated | | Spinner | @mdigitalcn/uikit/spinner | 4 variants, sizes | | Skeleton | @mdigitalcn/uikit/skeleton | shimmer, isLoaded state | | Result | @mdigitalcn/uikit/result | success/error/warning/info | | FetchingOverlay | @mdigitalcn/uikit/fetching-overlay | async loading mask |

| Component | Import path | Notes | |---|---|---| | Table | @mdigitalcn/uikit/table | virtualized, sortable, sticky, selection | | Tree | @mdigitalcn/uikit/tree | async load, drag, checkboxes | | Calendar | @mdigitalcn/uikit/calendar | min/max, disabled dates, keyboard nav | | Descriptions | @mdigitalcn/uikit/descriptions | responsive columns, bordered | | Timeline | @mdigitalcn/uikit/timeline | alternating, custom icons | | Transfer | @mdigitalcn/uikit/transfer | filterable, virtualized | | Carousel | @mdigitalcn/uikit/carousel | fade/coverflow, autoplay, thumbs | | Image | @mdigitalcn/uikit/image | preview, lazy, fallback | | QRCode | @mdigitalcn/uikit/qr-code | size, color, logo |

| Component | Import path | Notes | |---|---|---| | Badge | @mdigitalcn/uikit/badge | dot, count, max, position | | Tag | @mdigitalcn/uikit/tag | closable, icon, 4 variants | | Avatar | @mdigitalcn/uikit/avatar | image/initials/icon, group | | Kbd | @mdigitalcn/uikit/kbd | keyboard key display | | Typography | @mdigitalcn/uikit/typography | prose scale, mark, code, blockquote | | Watermark | @mdigitalcn/uikit/watermark | canvas-based, tiled |

| Export | Import path | |---|---| | ThemeProvider, useTheme, getThemeScript | @mdigitalcn/uikit/theme | | useControllable | @mdigitalcn/uikit/hooks | | useDebounce | @mdigitalcn/uikit/hooks | | useThrottle | @mdigitalcn/uikit/hooks | | useMediaQuery | @mdigitalcn/uikit/hooks | | useRipple | @mdigitalcn/uikit/hooks | | useMenuNavigation | @mdigitalcn/uikit/hooks | | useOverlayContainer | @mdigitalcn/uikit/hooks |


Overlay Nesting

A common source of bugs in component libraries: dropdown menus inside modals and drawers stop scrolling, lose focus, or close unexpectedly. This library solves all three.

How it works

ModalContent and DrawerContent set a React context (OverlayContainerContext) with a ref to their DOM node. Every PopoverContent and TooltipContent reads this context. When inside an overlay, they portal into the overlay's DOM tree instead of document.body.

This keeps dropdowns inside react-remove-scroll's allowed scroll zone (fixing wheel-scroll) and inside vaul's inert boundary (fixing pointer-event blocking). data-vaul-no-drag on every popover prevents vaul from treating scrolling inside a dropdown as a swipe-to-close gesture.

// This just works — no configuration needed
<ComposedModal open={open} onOpenChange={setOpen} title="Settings">
  <Select label="Country" options={countries} />
  <MultiSelect label="Tags" options={tags} />
  <DatePicker label="Start date" />
  <Dropdown items={actions} trigger={<Button>Actions</Button>} />
</ComposedModal>
// Same for Drawer
<ComposedDrawer open={open} direction="right" title="Filters">
  <Select label="Status" options={statuses} />
  <DatePicker label="Date range" />
</ComposedDrawer>

Building custom overlays

If you build your own overlay and want the same behavior, wrap content with the context provider:

import { OverlayContainerContext } from '@mdigitalcn/uikit/hooks'
import { useState, useRef } from 'react'

function MyCustomOverlay({ children }: { children: React.ReactNode }) {
  const [container, setContainer] = useState<Element | null>(null)

  return (
    <div ref={setContainer} className="my-overlay">
      <OverlayContainerContext.Provider value={container}>
        {children}
      </OverlayContainerContext.Provider>
    </div>
  )
}

CSS Variables Reference

/* Brand */
--color-primary            --color-primary-hover
--color-primary-active     --color-primary-foreground
--color-secondary          --color-secondary-hover
--color-secondary-active   --color-secondary-foreground
--color-accent             --color-accent-hover
--color-accent-active      --color-accent-foreground

/* Semantic */
--color-success            --color-success-hover
--color-success-active     --color-success-foreground
--color-error              --color-error-hover
--color-error-active       --color-error-foreground
--color-warning            --color-warning-hover
--color-warning-active     --color-warning-foreground
--color-info               --color-info-hover
--color-info-active        --color-info-foreground

/* Surfaces */
--color-background         --color-background-secondary
--color-surface            --color-card
--color-overlay

/* Text */
--color-text-primary       --color-text-secondary
--color-text-muted         --color-text-disabled

/* Borders */
--color-border             --color-border-primary
--color-border-hover       --color-border-focus

All radii cascade from --radius. Override --radius to shift everything at once, or override individual tokens for precise control:

--radius           /* Base — controls the global radius scale */

--radius-button    /* Buttons, chips, small interactive elements */
--radius-input     /* Text inputs, selects, textareas */
--radius-tag       /* Tags, badges, small labels */
--radius-card      /* Cards, panels */
--radius-dropdown  /* Dropdown menus, option lists */
--radius-popover   /* Popover content */
--radius-modal     /* Dialogs, modals */
--radius-drawer    /* Drawer panels */
--radius-tooltip   /* Tooltip bubbles */
/* Heights */
--button-height-xs/sm/md/lg
--input-height-xs/sm/md/lg
--select-height-xs/sm/md/lg
--toggle-height-xs/sm/md/lg
--checkbox-size-xs/sm/md/lg
--switch-width-xs/sm/md/lg      --switch-height-xs/sm/md/lg
--otp-size-xs/sm/md/lg

/* Horizontal padding */
--button-padding-x-xs/sm/md/lg
--input-padding-x-xs/sm/md/lg
--select-padding-x-xs/sm/md/lg
--toggle-padding-x-xs/sm/md/lg

/* Component-specific */
--textarea-min-height-xs/sm/md/lg
--accordion-padding-x-xs/sm/md/lg
--modal-max-width-xs/sm/md/lg
--drawer-width-sm/md/lg/xl/full
--duration-enter   /* Enter animation duration (default 150ms) */
--duration-exit    /* Exit animation duration (default 100ms) */
--ease-default     /* Default easing curve */

Control animation globally:

:root {
  --duration-enter: 200ms;
  --duration-exit:  120ms;
}
--z-dropdown   1000    /* Floating menus */
--z-sticky     1020    /* Sticky headers */
--z-modal      1040    /* Modals and drawers */
--z-popover    1050    /* Popovers, date pickers, selects */
--z-tooltip    1060    /* Tooltips */
--z-overlay    1070    /* Full-screen overlays */
--shadow-sm    --shadow-md    --shadow-lg
--shadow-xl    --shadow-2xl

TypeScript

Full type coverage. Every component prop, variant union, and callback is typed:

import type {
  ButtonProps,
  InputProps,
  SelectOption,
  TableColumn,
  ModalProps,
  DrawerProps,
  TooltipProps,
  CalendarProps,
} from '@mdigitalcn/uikit'

Strict mode compatible. No any in public types.


Browser Support

Modern evergreen browsers — Chrome, Firefox, Safari, Edge — last 2 major versions.

Uses oklch() for color tokens (supported in all evergreen browsers since 2023). Uses @layer and CSS custom properties throughout.


License

MIT — free for personal and commercial use.