@usereq/ui
v0.1.4
Published
UserEq design system — Radix Nova (shadcn), Tailwind CSS v4, documented with Storybook.
Readme
@usereq/ui
Design system for UserEq: React components built on Tailwind CSS v4, Radix UI (via the radix-ui meta-package), the shadcn v4 registry (radix-nova style), class-variance-authority, lucide-react, and next-themes (where components need theme context).
Components are published as TypeScript + TSX source (not a precompiled dist/). Your application bundler (Next.js, Vite, etc.) compiles them. That matches how shadcn-style libraries are usually consumed and keeps tree-shaking straightforward.
Table of contents
- What you get on npm
- Repository layout
- Dependencies
- Installation
- Package exports and import paths
- Use in a Next.js app (App Router)
- Use in a Vite app
- Tailwind CSS v4 and
@source - Dark mode
- TypeScript
- Storybook
- Scripts reference
- Developing and extending this package
- Publishing to npm
- GitHub Actions
- Troubleshooting
- License
What you get on npm
The "files" field in package.json controls the published tarball. Included:
| Path | Role |
|------|------|
| src/ | All components, hooks, lib helpers, and src/styles/globals.css. |
| postcss.config.mjs | Shared PostCSS config (Tailwind v4 PostCSS plugin). |
| components.json | shadcn CLI schema and aliases for this design system. |
| README.md | This file. |
Not included (development-only; clone the git repository for these):
stories/— Storybook CSF stories and MDX..storybook/— Storybook configuration.vite.config.ts— Vite config used by Storybook only.storybook-static/— Output ofnpm run build-storybook(also gitignored).package-lock.json— Present in the repo for reproducible installs; npm does not publish it as a runtime artifact (lockfile is for the package repo, not consumers).
Repository layout
ui/
├── .gitignore
├── .storybook/
│ ├── main.ts # Storybook + Vite + story roots
│ └── preview.tsx # Global CSS, theme toolbar, layout wrapper
├── components.json # shadcn registry: radix-nova; aliases @/* → src/ (see Developing)
├── package.json
├── package-lock.json
├── postcss.config.mjs # @tailwindcss/postcss — re-exported as @usereq/ui/postcss.config
├── README.md
├── stories/ # Storybook only (not on npm)
│ ├── Introduction.mdx
│ ├── *.stories.tsx
├── src/
│ ├── components/ # One file per component (flat)
│ ├── hooks/
│ ├── lib/
│ └── styles/
│ └── globals.css # Tailwind entry + design tokens (@theme, :root, .dark)
├── storybook-static/ # Generated; gitignored
└── vite.config.ts # Vite + PostCSS for StorybookDependencies
Peer dependencies (your app must install these)
| Package | Range | Why |
|---------|--------|-----|
| react | ^18 \|\| ^19 | All interactive UI is React. |
| react-dom | ^18 \|\| ^19 | Required with React. |
Bundled with @usereq/ui (you do not need to duplicate these for basic usage)
| Package | Role |
|---------|------|
| class-variance-authority | Variant APIs (buttonVariants, etc.). |
| clsx | Class name joining. |
| lucide-react | Icons used inside components. |
| next-themes | Used where components expect theme integration (e.g. patterns aligned with app theming). |
| radix-ui | Radix primitives (imported as radix-ui per shadcn v4 / Nova stack). |
| shadcn | Supplies shadcn/tailwind.css pulled in by globals.css. |
| tailwind-merge | twMerge inside cn(). |
| tw-animate-css | Animation utilities referenced from global CSS. |
Your app still needs Tailwind v4 and @tailwindcss/postcss in its own toolchain so that your pages and layouts are compiled; see Use in a Next.js app and Tailwind CSS v4 and @source.
Installation
npm install @usereq/ui react react-dompnpm add @usereq/ui react react-domyarn add @usereq/ui react react-dombun add @usereq/ui react react-domThen add Tailwind v4 + PostCSS to the app if they are not already there (Next.js “Tailwind” template includes them).
Package exports and import paths
Subpath exports are defined in package.json under "exports". Only these import forms are supported (extensionless, as published):
Styles
| Import | Resolves to |
|--------|-------------|
| @usereq/ui/globals.css | src/styles/globals.css |
Import once at your application root (e.g. root layout.tsx in Next.js).
PostCSS
| Import | Resolves to |
|--------|-------------|
| @usereq/ui/postcss.config | postcss.config.mjs |
Re-export from your app’s postcss.config.mjs so Tailwind runs on your app and on files under node_modules/@usereq/ui when your bundler pulls them in.
Utilities
| Import pattern | Example |
|----------------|---------|
| @usereq/ui/lib/* | @usereq/ui/lib/utils → src/lib/utils.ts |
cn() — merges Tailwind classes with tailwind-merge + clsx:
import { cn } from "@usereq/ui/lib/utils";Hooks
| Import pattern | Example |
|----------------|---------|
| @usereq/ui/hooks/* | @usereq/ui/hooks/use-mobile → src/hooks/use-mobile.ts |
Components (flat src/components/*.tsx)
Every src/components/<name>.tsx file is exposed as:
@usereq/ui/components/<name> (no .tsx suffix).
Examples: @usereq/ui/components/button, @usereq/ui/components/sidebar, @usereq/ui/components/logo. See the src/components/ directory for the full list (it grows when you run shadcn add).
Inside the package, components import each other with relative paths (for example ../lib/utils, ./badge) so published code does not rely on TypeScript paths for @usereq/ui/*.
Use in a Next.js app (App Router)
1. Install packages
npm install @usereq/ui react react-domEnsure the app uses Tailwind v4 (e.g. tailwindcss, @tailwindcss/postcss in devDependencies).
2. PostCSS
Create or replace postcss.config.mjs at the app root:
export { default } from "@usereq/ui/postcss.config";3. Transpile the design system
Next.js must compile the TypeScript sources from node_modules/@usereq/ui:
// next.config.ts or next.config.mjs
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
transpilePackages: ["@usereq/ui"],
};
export default nextConfig;4. Global CSS
In app/layout.tsx (or src/app/layout.tsx):
import "@usereq/ui/globals.css";Place it above any app-specific global CSS that might override tokens.
5. Use components
import { Button } from "@usereq/ui/components/button";
import { Card, CardHeader, CardTitle, CardContent } from "@usereq/ui/components/card";6. Optional: next/font and CSS variables
globals.css uses --font-sans inside @theme inline. You can map Geist or Inter in your layout:
import { GeistSans } from "geist/font/sans";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={GeistSans.variable}>
<body>{children}</body>
</html>
);
}Wire the variable name to match what your globals.css or app theme expects, or extend @theme in your own CSS layer.
Use in a Vite app
Install
react,react-dom,@usereq/ui,tailwindcss,@tailwindcss/postcss,typescript,@vitejs/plugin-reactas usual for a React + Vite + Tailwind v4 setup.postcss.config.mjs:export { default } from "@usereq/ui/postcss.config";In
vite.config.ts, ensure React plugin is enabled and dependencies are optimized as needed:import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [react()], });In your entry (e.g.
main.tsx):import "@usereq/ui/globals.css";Import components from
@usereq/ui/components/...as in the table above.
Tailwind CSS v4 and @source
src/styles/globals.css uses Tailwind v4’s CSS-first config:
@import "tailwindcss",tw-animate-css,shadcn/tailwind.css@custom-variant dark (&:is(.dark *));— dark mode when an ancestor has classdark@source— tells Tailwind which files to scan for class names
This repo’s globals.css includes @source entries for this package’s src/, and (for local Storybook) paths under stories/ and .storybook/. Paths are relative to the CSS file.
In your application, Tailwind must also see your route and component files, or classes used only in your app will be purged. Typical approaches:
Own CSS entry in the app that adds:
@import "tailwindcss"; @source "./app/**/*.{ts,tsx}"; @source "./components/**/*.{ts,tsx}";…and either import
@usereq/ui/globals.cssas a second stylesheet or merge token definitions as needed; orSingle app
globals.cssthat imports design tokens from@usereq/uiand duplicates minimal@sourcelines for your folders (copy the pattern from this repo).
If something is not styled, first verify PostCSS is applied to both app files and node_modules/@usereq/ui, then verify @source includes every directory where you write Tailwind classes.
Dark mode
Tokens define a .dark selector in globals.css. Add the class dark on <html> or a wrapper (for example with next-themes ThemeProvider with attribute="class" in your app). Storybook uses @storybook/addon-themes with themes "" and "dark" for quick checks.
TypeScript
Consumers normally need:
"moduleResolution": "bundler"or"node16"/"nodenext"(depending on your framework templates)"jsx": "preserve"or"react-jsx"as appropriate
You do not need a path alias for @usereq/ui in the consumer tsconfig if you import using the package subpaths (@usereq/ui/components/button, etc.); Node resolution follows package.json "exports".
This repo’s own tsconfig.json uses "paths": { "@/*": ["./src/*"] } for Storybook and local tooling only.
Storybook
| Item | Detail |
|------|--------|
| Version | Storybook 8.4.7 (pinned). @storybook/react-vite drives the preview; Vite 6 builds it. |
| Addons | @storybook/addon-docs (MDX + autodocs), @storybook/addon-themes (light/dark). |
| Stories | stories/**/*.stories.tsx, stories/**/*.mdx. |
| Port | 6006 (dev server). |
| Design tokens in preview | .storybook/preview.tsx imports ../src/styles/globals.css and wraps stories in a padded, bg-background surface. |
Commands
npm install
npm run storybookOpens the dev UI at http://localhost:6006.
npm run build-storybookWrites a static site to storybook-static/. Deploy that folder to any static host (Cloudflare Pages, S3 + CloudFront, Netlify, GitHub Pages) or upload it to Chromatic for hosted Storybook and visual review.
Why env -u NODE_OPTIONS?
On some machines, Yarn Plug’n’Play is enabled globally (for example a ~/.pnp.cjs or a shell profile that sets NODE_OPTIONS to preload PnP). Storybook’s manager bundle is built with esbuild, which can then enforce PnP rules and fail with errors like “Plug’n’Play manifest forbids importing …”. The npm scripts clear NODE_OPTIONS only for the Storybook process so local development stays reliable.
Manager build warning: radix-ui
You may see: unable to find package.json for radix-ui. The radix-ui package on npm is a meta-package; this warning is benign and the Storybook build still completes.
Storybook and dependency versions
Keep storybook and every @storybook/* package on the same patch version (currently 8.4.7). Mixing Storybook 8.4 with a newer @storybook/addon-docs (for example 8.6+) has previously broken the manager with missing storybook/internal/* modules.
Scripts reference
| Script | Command | Purpose |
|--------|---------|---------|
| check-types | tsc --noEmit | Typecheck src/, stories/, .storybook/, vite.config.ts. |
| storybook | env -u NODE_OPTIONS storybook dev -p 6006 | Local Storybook. |
| build-storybook | env -u NODE_OPTIONS CI=true storybook build -o storybook-static --disable-telemetry | Static build; non-interactive (CI=true); telemetry disabled for CI. |
Developing and extending this package
Prerequisites
- Node.js LTS recommended.
- npm (this repository uses
package-lock.json).
shadcn CLI and components.json
components.json is set up for radix-nova, RSC, TSX, and Lucide. Aliases use @/ (for example @/components, @/lib/utils, @/hooks) which match tsconfig.json "paths": { "@/*": ["./src/*"] }.
Why not @usereq/ui/... in components.json? The shadcn CLI treats alias values as filesystem paths under the project root. If you set "components": "@usereq/ui/components", the CLI literally creates a folder named @usereq/ui/components/ next to package.json instead of writing into src/components/. Consumers still import the published package as @usereq/ui/components/... via package.json "exports"; that is unrelated to components.json aliases.
To add a registry component into this repo (from the ui/ root):
npx shadcn@latest add button -y
# or multiple:
npx shadcn@latest add sheet dialog -yUse -c / --cwd if your shell is elsewhere:
npx shadcn@latest add sheet -y --cwd /path/to/uiNew files should land under src/components/ (or src/hooks/). After adding, check imports: the CLI may emit @/lib/utils style imports; for npm publishing, prefer relative imports between package files (see below). Re-run npm run check-types and add or update stories under stories/ so Storybook stays the visual source of truth.
Internal imports
Between files inside src/, use relative paths (../lib/utils, ./label, ../hooks/use-mobile) so the published tarball works in apps that do not define a @/* path alias for this package. If the CLI generated @/... imports, rewrite them to relatives before publishing.
Publishing to npm
Prerequisites
- An npm organization (for example
@usereq) and permission to publish scoped packages. - Two-factor authentication on npm, or a granular access token with publish rights and bypass 2FA for automation (npm enforces this for publishes).
Steps
Update the version in
package.json(SemVer:patch/minor/major).Dry run the tarball:
npm pack --dry-runConfirm the file list matches
"files"(no accidental inclusion ofstories/,.storybook/, or secrets).Login (interactive) or use a token in
~/.npmrc://registry.npmjs.org/:_authToken=${NPM_TOKEN}Publish a public scoped package:
npm publish --access publicpublishConfig.accessis already"public"inpackage.json; the flag is still safe for clarity.Verify on
https://www.npmjs.com/package/@usereq/ui?activeTab=code.
After publish
Bump the app dependency on @usereq/ui and run your app’s install + test pipeline.
GitHub Actions
Workflows live under .github/workflows/ in this repository (repo root = package root).
| Workflow | Trigger | What it does |
|----------|---------|----------------|
| ci.yml | Push / PR to main or master | npm ci, npm run check-types, npm run build-storybook. |
| publish.yml | Push a version tag matching v* (e.g. v0.1.1) | Same steps, then npm publish --access public. |
Repository secret: add NPM_TOKEN (npm automation or granular token with publish rights for @usereq/ui, and bypass 2FA if required). GitHub → Settings → Secrets and variables → Actions.
Release flow: bump version in package.json, commit, tag, push the tag:
git tag v0.1.2
git push origin v0.1.2If this package ever lives inside a monorepo, add defaults.run.working-directory to each job or move these files under the package subfolder and adjust paths.
Troubleshooting
| Symptom | Things to check |
|---------|------------------|
| “Module not found” for @usereq/ui/... | Install command succeeded; package.json in the app lists @usereq/ui; delete node_modules and lockfile cache and reinstall. |
| Components render without styles | import "@usereq/ui/globals.css" in the app root; PostCSS re-exports @usereq/ui/postcss.config; Tailwind @source includes your app paths; Next.js has transpilePackages: ["@usereq/ui"]. |
| Storybook: PnP / “forbids importing” | Scripts already use env -u NODE_OPTIONS. Align all Storybook packages to the same 8.4.7 version. |
| Storybook: storybook/internal/... not found | Version skew between storybook and @storybook/addon-docs — pin all to 8.4.7 and npm ci. |
| Types from deep imports | Use the documented subpaths only; avoid reaching into unpublished paths under node_modules. |
| After shadcn add, a folder @usereq/ appears | components.json used "components": "@usereq/ui/...". The CLI writes files to that path literally. Use @/components (etc.) in components.json and move files into src/ — see Developing and extending. |
License
Add a LICENSE file at the package root when you are ready for public redistribution. Until then, treat usage as internal / all rights reserved unless your organization states otherwise.
