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-uiPeer 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-schemeon 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-950Spacing 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__spinnerNo 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 filesBuild
# Development (watch mode)
npm run dev
# Production build
npm run buildOutput:
dist/index.js— ESM bundledist/index.cjs— CommonJS bundledist/index.d.ts— TypeScript declarationsdist/style.css— All styles (reset + components + utilities)
License
MIT
