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

jtr-ui

v0.3.0

Published

React UI component library with SCSS theming

Readme

jtr-ui

React UI component library with custom SCSS theming, dark mode support, and full accessibility.

49 components | Zero Tailwind dependency | CSS variables for theming | Light & Dark mode | TypeScript

Live Preview & Documentation — Browse all components, variants, and themes in an interactive playground.


Installation

npm install jtr-ui

Peer dependencies

npm install react react-dom lucide-react motion

| Package | Version | |---|---| | react | ^18.0.0 || ^19.0.0 | | react-dom | ^18.0.0 || ^19.0.0 | | lucide-react | >=0.400.0 | | motion | >=12.0.0 |


This includes all component styles, design tokens, CSS variables, and dark mode support. No extra configuration needed.

Note: If you use dark: prefix in your own Tailwind classes (not jtr-ui components), also add @custom-variant dark (&:is(.dark *)); to your CSS.

Quick Start

import { Button, Card, Input } from 'jtr-ui';
import 'jtr-ui/style.css';

function App() {
  return (
    <Card shadow>
      <Input label="Email" placeholder="[email protected]" />
      <Button variant="primary">Submit</Button>
    </Card>
  );
}

The stylesheet import (jtr-ui/style.css) includes everything — reset, typography, font (Plus Jakarta Sans), CSS variables, utility classes, and all component styles.


Dark Mode

All components automatically adapt when the .dark class is present on the root element.

Basic toggle

document.documentElement.classList.toggle('dark');

Full implementation (recommended)

This example respects system preferences, persists the choice in localStorage, and provides a toggle button:

import { useState, useEffect } from 'react';
import { Button } from 'jtr-ui';
import { Moon, Sun } from 'lucide-react';

function ThemeToggle() {
  const [dark, setDark] = useState(() => {
    const saved = localStorage.getItem('theme');
    if (saved) return saved === 'dark';
    return window.matchMedia('(prefers-color-scheme: dark)').matches;
  });

  useEffect(() => {
    document.documentElement.classList.toggle('dark', dark);
    localStorage.setItem('theme', dark ? 'dark' : 'light');
  }, [dark]);

  return (
    <Button variant="secondary" size="sm" onClick={() => setDark(!dark)}>
      {dark ? <Sun size={16} /> : <Moon size={16} />}
      {dark ? 'Light' : 'Dark'}
    </Button>
  );
}

This gives you:

  • System preference — respects prefers-color-scheme on first visit
  • Persistence — remembers the choice after page refresh via localStorage
  • Toggle button — users can switch manually at any time

Theming

All styling is driven by CSS custom properties. Override them to customize the entire library:

:root {
  /* Change primary color to blue */
  --color-primary: #3b82f6;
  --color-primary-foreground: #ffffff;
  --color-primary-gradient: linear-gradient(to right, #3b82f6, #2563eb);
  --color-primary-glow: rgba(59, 130, 246, 0.2);
  --color-primary-soft: #eff6ff;
  --color-primary-border: rgba(59, 130, 246, 0.35);

  /* Change border radius */
  --radius-xl: 1rem;
  --radius-2xl: 1.5rem;

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

Color palette (Tailwind-like shades)

Each color has shades from 50 to 950:

--color-primary-50 through --color-primary-950
--color-neutral-50 through --color-neutral-950
--color-destructive-50 through --color-destructive-950
--color-warning-50 through --color-warning-950
--color-info-50 through --color-info-950

Spacing scale

--spacing-1: 0.25rem   /* 4px */
--spacing-2: 0.5rem    /* 8px */
--spacing-3: 0.75rem   /* 12px */
--spacing-4: 1rem      /* 16px */
--spacing-5: 1.25rem   /* 20px */
--spacing-6: 1.5rem    /* 24px */
--spacing-8: 2rem      /* 32px */
--spacing-10: 2.5rem   /* 40px */
--spacing-12: 3rem     /* 48px */

Half-steps also available: --spacing-0-5, --spacing-1-5, --spacing-2-5, --spacing-3-5.


Utility Classes

The library includes utility classes similar to Tailwind, powered by CSS variables:

<!-- Spacing -->
<div class="m-4 p-2 gap-3">
<div class="mt-2 mr-4 mb-6 ml-auto">
<div class="px-4 py-2">

<!-- Display & Flex -->
<div class="flex items-center justify-between gap-4">
<div class="grid hidden">

<!-- Typography -->
<span class="text-sm font-bold uppercase">
<p class="text-center text-muted">

<!-- Colors -->
<div class="bg-primary text-primary-foreground">
<div class="text-primary-500 bg-neutral-100">

<!-- Borders & Shadows -->
<div class="rounded-xl shadow-md">

<!-- Other -->
<div class="cursor-pointer opacity-50 select-none">

Components

Form

| Component | Description | Key Props | |---|---|---| | Button | Button with variants and sizes | variant, size, loading, disabled | | Input | Text input with label and error | label, error, disabled | | Textarea | Multi-line text input | label, error, rows | | Select | Dropdown select with search | options, value, onChange, label | | Checkbox | Checkbox with label | checked, onChange, label | | Radio | Radio button group | options, value, onChange | | Switch | Toggle switch | checked, onChange, label | | Slider | Range slider | value, onChange, min, max, label | | TagInput | Tag input with add/remove | tags, onChange, maxTags | | FileUpload | Drag & drop file upload | onUpload, accept, maxSize | | InputOTP | One-time password input | value, onChange, length, separator | | Toggle | Toggle button | pressed, onChange, variant | | ToggleGroup | Group of toggle buttons | items, value, onChange, multiple |

Display

| Component | Description | Key Props | |---|---|---| | Card | Content card container | variant, shadow | | Badge | Status/label badge | variant, dot | | Alert | Notification alert | variant, title, description | | Progress | Progress bar | value, max, label | | Skeleton | Loading placeholder | variant, width, height | | StatCard | Statistics card | label, value, change, icon | | EmptyState | Empty state placeholder | icon, title, description, action | | AvatarGroup | Group of avatars | avatars, max | | Divider | Horizontal divider | label |

Navigation

| Component | Description | Key Props | |---|---|---| | Tabs | Tab navigation | tabs, defaultIndex | | Breadcrumb | Breadcrumb navigation | items | | Pagination | Page navigation | currentPage, totalPages, onPageChange | | Stepper | Step indicator | steps, currentStep, onStepClick | | Sidebar | Side navigation | items, activeId, collapsible | | Menubar | Menu bar | menus |

Overlay

| Component | Description | Key Props | |---|---|---| | Dialog | Modal dialog | open, onClose, title | | AlertDialog | Confirmation dialog | open, onClose, onConfirm, variant | | Sheet | Slide-in panel | open, onClose, side, title | | Dropdown | Dropdown menu | trigger, items, align | | Tooltip | Tooltip on hover/click | content, side | | Popover | Popover with custom content | trigger, children, align | | HoverCard | Card on hover | trigger, children, side | | ContextMenu | Right-click menu | items, children |

Data

| Component | Description | Key Props | |---|---|---| | Table | Data table with search, filter, scroll | columns, data, searchable, onRowClick | | Calendar | Date picker calendar | selected, onSelect, minDate, maxDate | | SearchAutocomplete | Search with autocomplete | items, onSelect, placeholder | | Carousel | Slide carousel | slides, autoPlay, interval |

Layout

| Component | Description | Key Props | |---|---|---| | Accordion | Expandable sections | items, multiple, defaultOpen | | Collapsible | Single collapsible section | trigger, children, defaultOpen | | Resizable | Resizable split panels | firstPanel, secondPanel, direction | | ScrollArea | Custom scrollable area | maxHeight, orientation |

Notification

| Component | Description | Key Props | |---|---|---| | NotificationBadge | Badge wrapper with count/dot | count, dot, children | | NotificationPopover | Notification bell with popup | notifications, onMarkAllRead, onDismiss | | showToast | Toast notification function | variant, title, description | | BackToTop | Scroll to top button | variant, showAfter, label | | FAB | Floating action button | icon, label, variant, position |


Component Principles

Liskov Substitution

All base components extend native HTML attributes. You can pass any native prop:

<Button type="submit" form="my-form" aria-label="Save">
  Save
</Button>

<Input name="email" autoComplete="email" required />

className Merge

Every component accepts className that extends (not replaces) the default classes:

<Button className="mr-2">Spaced Button</Button>
<Card className="mt-4 p-8">Custom padding</Card>

forwardRef

All relevant components forward refs to the underlying DOM element:

const inputRef = useRef<HTMLInputElement>(null);
<Input ref={inputRef} label="Focus me" />

BEM Class Names

All CSS classes use the jtr- prefix with BEM naming:

.jtr-button
.jtr-button--primary
.jtr-button--sm
.jtr-button__spinner

No class name collisions with your existing CSS.


Table (Advanced)

The Table component includes built-in search, column filtering, text truncation with tooltips, and horizontal scroll buttons:

<Table
  searchable
  searchPlaceholder="Search users..."
  columns={[
    { key: 'name', label: 'Name' },
    { key: 'email', label: 'Email', truncate: 20 },
    { key: 'role', label: 'Role', filterable: true },
    { key: 'status', label: 'Status', filterable: true },
  ]}
  data={[
    { name: 'Alice', email: '[email protected]', role: 'Admin', status: 'Active' },
    { name: 'Bob', email: '[email protected]', role: 'Editor', status: 'Away' },
  ]}
  onRowClick={(row) => console.log(row)}
/>

| Column Prop | Description | |---|---| | filterable | Shows a filter dropdown with unique values | | truncate | Max characters before truncating with tooltip | | align | "left" (default) or "right" |


Toast

Custom toast system with no external dependencies. Add <Toaster /> to your app root:

import { Toaster, showToast } from 'jtr-ui';

function App() {
  return (
    <>
      <Toaster position="bottom-right" />
      <Button onClick={() => showToast({
        variant: 'success',
        title: 'Saved!',
        description: 'Your changes have been saved.',
      })}>
        Save
      </Button>
    </>
  );
}

Variants: success, error, info, default. Positions: top-right, top-left, bottom-right, bottom-left, top-center, bottom-center.

Cookie Banner

import { CookieBanner } from 'jtr-ui';

<CookieBanner
  position="bottom-right"
  onAccept={(prefs) => console.log('Accepted:', prefs)}
  onReject={(prefs) => console.log('Rejected:', prefs)}
  categories={[
    { key: 'necessary', label: 'Necessary', description: 'Required for the site.', icon: Shield, required: true },
    { key: 'analytics', label: 'Analytics', description: 'Usage tracking.', icon: BarChart3 },
  ]}
/>

Features: Accept/Reject buttons, settings panel with toggleable categories, localStorage persistence, customizable texts and delay.


SCSS Architecture

The library uses a 7-1 SCSS pattern:

src/scss/
├── style.scss              <- single entry point
├── utils/
│   ├── _variables.scss     <- CSS custom properties
│   ├── _mixins.scss        <- breakpoints, focus-ring, etc.
│   └── _functions.scss     <- rem(), em()
├── base/
│   ├── _reset.scss         <- modern CSS reset
│   └── _typography.scss    <- font import + headings
├── layout/
│   ├── _container.scss     <- responsive container
│   └── _grid.scss          <- 12-column grid
├── utilities/
│   ├── _spacing.scss       <- .m-*, .p-*, .gap-*
│   ├── _display.scss       <- flex, grid, text, radius, shadows
│   └── _colors.scss        <- .text-*, .bg-*
└── components/
    └── 37 component SCSS files

Build

# Development (watch mode)
npm run dev

# Production build
npm run build

Output:

  • dist/index.js — ESM bundle
  • dist/index.cjs — CommonJS bundle
  • dist/index.d.ts — TypeScript declarations
  • dist/style.css — All styles (reset + components + utilities)

License

MIT