@craftzbay/ui
v1.0.2
Published
Refined-minimal Tailwind v4 + React design system — 50+ accessible, themeable primitives.
Maintainers
Readme
@craftzbay/ui
A refined-minimal Tailwind v4 + React design system. Production-grade primitives — Button through DataGrid — plus composed patterns (authentication, app shell, settings, etc.).
- Showcase: ui.craftzbay.com
- Components: ui.craftzbay.com#components
- Templates: ui.craftzbay.com#templates
Aesthetic direction: Linear / Vercel / Stripe Dashboard / Notion / Raycast. Neutral-dominant, one accent, hairline borders, generous whitespace, fast quiet motion. See
docs/PHILOSOPHY.md.
Install
pnpm add @craftzbay/ui # peers: react ^18 || ^19, react-dom ^18 || ^191. Fonts — Geist + Geist Mono are referenced by the tokens but not bundled (Inter is only a fallback in the stack). Google Fonts serves the cyrillic-ext subset automatically; self-hosting (≤4 woff2, weights 400/500/600) is equally fine. Add to <head>:
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500&display=swap"
rel="stylesheet"
/>2. CSS — pick one:
/* a) No Tailwind in your app: one precompiled sheet (tokens + base + every utility the components use) */
@import '@craftzbay/ui/styles.css';
/* b) Your app already uses Tailwind v4: share the tokens and let Tailwind compile the library's classes */
@import 'tailwindcss';
@import 'tw-animate-css'; /* required on this path — overlays use animate-in / animate-out */
@import '@craftzbay/ui/theme.css';
@source "../node_modules/@craftzbay/ui/dist-lib";Path (b) needs tw-animate-css installed (pnpm add -D tw-animate-css; it is an optional peer). Path (a) already bundles it.
3. Dark mode — toggle the dark class on <html>; every token flips and color-scheme follows. Set it from a blocking script before first paint to avoid a flash (three states: light / dark / system). data-theme is not used.
4. Providers — <Toaster /> once near the root if you use toast(); wrap the app in <TooltipProvider> if you use Tooltip. Nothing else is required.
import { Button, Toaster, TooltipProvider, toast } from '@craftzbay/ui';
export function App() {
return (
<TooltipProvider>
<Button onClick={() => toast({ title: 'Saved' })}>Save</Button>
<Toaster />
</TooltipProvider>
);
}Next.js App Router — since 0.10 every built module carries a 'use client' banner, so import { Button } from '@craftzbay/ui' works directly inside Server Components without a local re-export file. Put the CSS import and fonts in app/layout.tsx. Note: the pure helpers (formatDate, formatNumber, formatMNT, mnStrings, defaultStrings) are also client-marked today — they still run fine in Server Components (the directive only affects the boundary), but they are not tree-shaken into a server-only chunk.
Name-addressed icons — Icons.* (curated, tree-shaken) ships in the main entry. The lazy <Icon name="…"> + iconNames list lives in a separate entry so its ~1500-icon import map never enters your bundle unless asked for:
import { Icon } from '@craftzbay/ui/icon';
<Icon name="calendar" className="size-4" />;The package is ESM-only, ships one module per component (sideEffects limited to CSS), so import { Button } pulls in about 8 KB gzipped — no calendar, drawer, or form dependencies.
Localisation (Mongolian built in)
Every built-in string (close/dismiss labels, placeholders, "No results.", pagination summary, error-state copy…) is typed in UiStrings and read through useStrings(). Defaults are English; a full Mongolian set ships as mnStrings.
import { DesignSystemProvider, mnStrings } from '@craftzbay/ui';
<DesignSystemProvider strings={mnStrings}>…</DesignSystemProvider>; // whole library in Mongolian
<DesignSystemProvider strings={{ dialog: { close: 'Schließen' } }}>…</DesignSystemProvider>; // partial override, deep-merged over defaultsPrecedence is per-component props (placeholder, labels, aria-label) → nearest provider → defaultStrings. Templates use {name} placeholders, e.g. pagination.showing: '{from}–{to} / {total}'.
Design rules
This library implements the craftzbay design-research guidelines
(rendered site) — colour, type, spacing, components, accessibility, tokens.
design-research is the source of truth (canonical numbers in 00-defaults.md);
docs/PHILOSOPHY.md is the library-specific summary.
Local development
This package lives in the craftzbay-ui
monorepo (packages/ui). The showcase site is a separate workspace
(apps/site) that consumes this package's source directly. From the repo root:
pnpm install
pnpm dev # showcase site (apps/site) — edits to this package hot-reload
pnpm build:lib # build this library → packages/ui/dist-lib/Inside packages/ui itself, pnpm build produces the distributable bundle and
pnpm test runs the component tests.
Tech stack
- Tailwind CSS v4 — tokens defined in
@themeinsrc/styles/theme.css(globals.css= tailwind + tw-animate-css + theme) - React 18 / 19 + TypeScript 5.7+
- Radix UI primitives for accessibility-correct overlays
- class-variance-authority +
cn()(clsx+tailwind-merge) - Lucide icons (16 / 20px, 1.5 stroke)
- Geist sans + Geist Mono monospace
Project structure
src/
├── styles/
│ ├── theme.css # @theme tokens, semantic vars, dark variant, base layer
│ └── globals.css # tailwindcss + tw-animate-css + theme.css
├── lib/
│ ├── utils.ts # cn() + uid()
│ ├── format.ts # formatDate / formatNumber / formatMNT
│ └── strings.ts # UiStrings, defaultStrings (+ strings.mn.ts)
├── components/
│ ├── ui/ # primitives — 52 components
│ └── patterns/ # composed layouts
├── hooks/
│ ├── use-toast.ts
│ └── use-media-query.ts
└── icons/ # curated Lucide re-exports
docs/
├── PHILOSOPHY.md # the 6 refined-minimal principles
├── VOICE.md # content + tone of voice
└── ACCESSIBILITY.md # WCAG checklist + contrast ratiosComponent index
Inputs
- Input — text / email / password / number / search with prefix, suffix, error
- Textarea — auto-resize multi-line input
- Select — single-choice menu (Radix)
- MultiSelect — chip-based multi-choice picker
- Combobox — searchable single-select, sync or async
- Checkbox — including indeterminate
- RadioGroup — mutually-exclusive choices
- Switch — instant-apply binary toggle
- Slider — single + range
- DatePicker — single + range
- Form primitives — react-hook-form bindings
Buttons
- Button — primary · secondary · outline · ghost · destructive · link
- IconButton — square icon-only variant
- Pagination — numbered + jumps + page-size
Feedback
- Alert — inline banner, dismissible
- Toast +
useToasthook - Spinner — accent / neutral / on-accent tones
- Progress — linear + circular, determinate + indeterminate
- Skeleton — text, avatar, card variants
- EmptyState
- ErrorState — 404 / 500 / generic
Navigation
- TopNav +
TopNavLink - Sidebar +
SidebarSection+SidebarItem+SidebarGroup - Breadcrumbs — with overflow ellipsis
- Tabs — underline + pills variants
- Stepper — horizontal + vertical
Layout
- Card +
CardHeader/Title/Description/Content/Footer - Separator
- ScrollArea — Radix-backed styled scroll
- Accordion — single + multiple
Overlays
- Dialog +
ConfirmationDialog - Sheet — left / right / top / bottom
- Popover
- Tooltip — 500ms delay default
- DropdownMenu — submenus, separators, kbd
- ContextMenu — right-click menu
- CommandPalette — ⌘K palette
Data display
- Table +
TableSortHeader - DataGrid — column visibility, filter, sortable
- Badge — subtle + outline, 6 tones
- Avatar +
AvatarGroup
Typography
- Kbd — keyboard shortcut indicator
Blocks (page templates)
Whole-page compositions — dashboard, settings, auth, pricing, data table,
record detail, onboarding, first-run — are not shipped as importable
components. They live in the showcase as copy-paste blocks: complete pages
assembled from the primitives above, with the full source on the page. Read it,
copy it, adapt it — no opaque <Dashboard /> import. Browse them under
Templates in the showcase.
Documentation
docs/PHILOSOPHY.md— the six principles + forbidden listdocs/VOICE.md— tone of voice, button labels, error copy formuladocs/ACCESSIBILITY.md— WCAG AA contrast table, keyboard map
Contributing
- Read
docs/PHILOSOPHY.mdfirst — the forbidden list is non-negotiable. - Components reference semantic tokens (
bg-card,text-accent), never raw palette steps (bg-indigo-500). - Every interactive element ships with: default, hover, focus-visible, active, disabled, loading, and (where applicable) error / success states.
- Forward refs correctly; set
displayName. - Run
pnpm typecheck && pnpm testbefore opening a PR.
