
@djangocfg/ui-core
Framework-agnostic React UI library: 70+ shadcn/Radix components on Tailwind v4, semantic theme tokens, palette hooks for Canvas/SVG, plus a router-adapter system that lets the same <Link> / <Sidebar> / <SSRPagination> work under Next.js, Vite, Electron, Wails, or plain React.
Install
pnpm add @djangocfg/ui-coreWorks in any React host. Next.js apps import components from here directly and
add the Next router adapter from @djangocfg/ui-core/adapters/nextjs (see
Router adapters below); Next-specific server utilities (sitemap, health,
OG images, config) live in the separate @djangocfg/nextjs package.
Import styles once at the app root — use the golden path, which pins everything to the right cascade layers so import order can't break layout utilities:
/* FIRST style import in your app entry CSS */
@import "@djangocfg/ui-core/styles/full"; /* Tailwind v4 + tokens + base + utilities, layer-safe */The plain @djangocfg/ui-core/styles entry does NOT import Tailwind and emits
its CSS unlayered — if you use it you own the layer ordering (import
tailwindcss first). See src/styles/README.md § App setup for why. Prefer
…/styles/full.
Quick start
import { UiProviders, Button, Card } from '@djangocfg/ui-core';
<UiProviders>
<Card><Button>Hello</Button></Card>
</UiProviders><UiProviders> mounts Tooltip / Dialog / Toast in the right order and a top-level error Boundary (a crash shows a recoverable fallback, not a white screen). Mount it once at the root — library components (and everything in @djangocfg/ui-tools) trust it to be there and never nest their own (a second TooltipProvider is the canonical "Tooltip must be used within TooltipProvider" trap). Pass onError to forward crashes to your logger, errorFallback for a custom (e.g. i18n) crash screen, or errorBoundary={false} to opt out.
Catalogue
| Group | Examples |
|---|---|
components/data/ |
Avatar · Badge · Card · Table · BalancedText · Skeleton |
components/forms/ |
Button · Input · Textarea · Select · Switch · Checkbox · Slider · Form |
components/feedback/ |
Alert · Toast · Banner · Progress · Spinner |
components/overlay/ |
Dialog · Drawer · Popover · Tooltip · HoverCard · Sheet · ContextMenu · DropdownMenu |
components/navigation/ |
Sidebar · Tabs · Breadcrumb · Pagination · NavigationMenu · Command |
components/layout/ |
Container · Grid · Stack · Separator · ScrollArea · Sticky |
components/select/ |
Combobox · MultiSelect |
components/effects/ |
Glass · Marquee · Backdrop |
components/specialized/ |
Accordion · Collapsible · Toggle · Calendar · DatePicker |
components/boundary/ |
ErrorBoundary |
Imports stay flat — group folders are organisational.
Hooks (/hooks)
| Topic | Hooks |
|---|---|
dom/ |
useSize · useResizeObserver · useMeasure · useMutationObserver · useIntersection |
device/ |
useIsMobile · useIsTouch · useMediaQuery · useOnline · useViewportSize · useOrientation |
state/ |
useLocalStorage · useSessionStorage · useToggle · useCounter · useDebouncedValue |
events/ |
useEventListener · useClickOutside · useKeyPress · useFocusWithin |
theme/ |
useTheme · useResolvedTheme · useThemePreset |
feedback/ |
useToast · useDialog · useClipboard · useConfirm |
hotkey/ |
useHotkey (single key + chord) |
audio/ |
useBeep · useSpeak (Web Speech) |
tabs/ |
useCrossTab (BroadcastChannel coordination) |
media/ |
useMediaPermissions · useUserMedia · useDevices |
time/ |
useNow · useInterval · useTimeout |
router/ |
useLink · useRouter (router-adapter consumer) |
Lib utilities (/lib)
cn(...)—clsx+tailwind-mergeshortcutgetIntensity(value, thresholds)— quantise values into discrete bins (heatmaps, gauges)createLogger()— leveled console loggerdialog-service— imperativeconfirm()/alert()/prompt()returning promisespersist— typed localStorage / sessionStorage hookspretext(subpath@djangocfg/ui-core/lib/pretext) — DOM-free text measurement via @chenglou/pretext; powers<BalancedText>and is the primitive for non-CSS line balancing
Router adapters
The router-aware components (Sidebar, Link, SSRPagination) read the active router via RouterAdapterProvider. Ship the adapter that matches your host:
| Adapter | Source |
|---|---|
| Next.js App Router | @djangocfg/ui-core/adapters/nextjs |
Plain <a> fallback |
default (no adapter) |
Theming (/styles)
Tailwind v4 with semantic tokens, not raw color scales:
<Card className="bg-card border-border text-foreground" />
<Button className="bg-primary text-primary-foreground" />
<Alert className="bg-warning-background text-warning-foreground border-warning-border" />Tokens live in :root / .dark as fully-wrapped CSS colors; @theme inline exposes them as --color-X references, so opacity modifiers (bg-card/40, border-foreground/20) resolve via color-mix for every semantic token.
@custom-variant dark (&:where(.dark, .dark *)) binds the dark: variant to the .dark class on <html> (not prefers-color-scheme) — every theme-switcher in this monorepo toggles that class.
Tailwind's text-* size utilities are bridged to the preset-overridable --font-size-* scale, so overriding those vars (globally via buildThemeStyleSheet({ vars }) or scoped on a selector) re-sizes all text-* at once — no per-component edits. See src/styles/README.md § Typography tokens.
Programmatic theme colors for Canvas / SVG / Mermaid:
import { useThemeColor, alpha, useStylePresets } from '@djangocfg/ui-core/styles/palette';
const primary = useThemeColor('primary'); // #00d9ff (hex, not oklch)
const dim = alpha(primary, 0.15); // 'rgba(0, 217, 255, 0.15)'
const { success, warning, danger } = useStylePresets();Always hex-strings — color-mix(...) / oklch(...) syntax is rejected by Canvas2D fillStyle.
Live playground covers all tokens, presets, and dark-mode pairs.
Maintenance rule
After any change to components or hooks — update this README and bump the package patch version. Consumers pin to npm versions; surface drift in this file is the canonical changelog.
Requirements
- React 18 or 19
- Tailwind CSS v4 (host imports
@djangocfg/ui-core/styles)
License
MIT — djangocfg.