@curetcore/ui
v3.6.6
Published
Design system compartido para productos Curetcore
Maintainers
Readme
@curetcore/ui
Shared design system for the Curetcore product ecosystem. One package, one visual identity, every product.
Vision
@curetcore/ui is the visual infrastructure that connects all Curetcore products. A user moving between Linkship, Karrito, Clicky, or Logoeassy should feel they are in the same ecosystem — same motion, same glassmorphism, same attention to detail. Each product changes one CSS variable and everything adapts.
This package was born from Linkship, the most polished product in the family. It grows organically: components are extracted when they prove themselves in production across 2+ products, not designed in abstract. That makes it more robust than trying to predict what you will need.
The rule is simple: if a component is used in 2+ products, it gets extracted here. If it lives in only one product, it stays local.
Ecosystem
| Product | Accent | Domain | Description | |---------|--------|--------|-------------| | Linkship | Violet | linkship.cc | Link-in-bio platform | | Karrito | Emerald | — | E-commerce tools | | Clicky | Cobalt | — | QR code generator | | Logoeassy | Amber | — | AI logo generator |
Every product imports the same preset, overrides --color-accent-*, and gets a fully themed UI out of the box.
Architecture
@curetcore/ui
├── css/ # CSS foundation (imported in globals.css)
│ ├── preset.css # Theme tokens, accent scale, typography, dark mode
│ ├── animations.css # 18 keyframes + 20 utility classes + reduced motion
│ └── utilities.css # Glassmorphism, shadows, hover effects, scrollbars
├── components/ # 13 React components (all "use client")
├── hooks/ # 3 hooks (useTheme, useDebounce, useCopyToClipboard)
├── provider/ # CuretcoreProvider (LazyMotion strict + MotionConfig)
├── theme/ # Color tokens, accent presets, easings, shadows
├── motion/ # Spring presets, animation variants (Framer Motion)
└── utils/ # cn() utility (clsx wrapper)Design principles
Glassmorphism as identity — Cards, overlays, and containers use backdrop blur with subtle borders. This is the visual signature that ties everything together.
Accent-driven theming — A single CSS variable scale (
--color-accent-50through--color-accent-950) plus a deep variant controls all color. Components reference accent tokens, never hardcoded colors.Motion with springs — All animations use physics-based springs via Framer Motion, not CSS timing functions. The
CuretcoreProviderenablesLazyMotion strictfor tree-shaking andMotionConfig reducedMotion="user"for accessibility.Server-safe exports —
theme,utils, andmotionsubpaths have no"use client"directive and can be imported in server components.components,hooks, andproviderare client-only.
Subpath exports
| Import path | Content | Client-only |
|-------------|---------|:-----------:|
| @curetcore/ui | Everything re-exported | Yes |
| @curetcore/ui/components | All 13 components + types | Yes |
| @curetcore/ui/hooks | useTheme, useDebounce, useCopyToClipboard | Yes |
| @curetcore/ui/provider | CuretcoreProvider | Yes |
| @curetcore/ui/theme | BRAND_COLORS, ACCENT_PRESETS, EASINGS, SHADOWS | No |
| @curetcore/ui/motion | springs, variants, staggerContainer | No |
| @curetcore/ui/utils | cn() | No |
| @curetcore/ui/css/preset.css | Theme tokens + base styles | — |
| @curetcore/ui/css/animations.css | Keyframes + utility classes | — |
| @curetcore/ui/css/utilities.css | Glass, shadows, scrollbars | — |
Installation
npm install @curetcore/uiPublished to GitHub Packages (
npm.pkg.github.com). Requires.npmrcwith@curetcore:registry=https://npm.pkg.github.com.
Peer dependencies
| Package | Version |
|---------|---------|
| react | ^18.2.0 or ^19.0.0 |
| react-dom | ^18.2.0 or ^19.0.0 |
| framer-motion | ^12.0.0 |
| lucide-react | ^0.400.0 |
| next | >=14.0.0 |
Setup
1. CSS (globals.css)
@import "@curetcore/ui/css/preset.css";
@import "@curetcore/ui/css/animations.css";
@import "@curetcore/ui/css/utilities.css";
/* Tailwind v4: scan component classes from the package */
@source "../node_modules/@curetcore/ui/dist/**/*.js";2. Provider (root layout)
import { CuretcoreProvider } from "@curetcore/ui/provider";
export default function RootLayout({ children }) {
return (
<html>
<body>
<CuretcoreProvider>{children}</CuretcoreProvider>
</body>
</html>
);
}The provider wraps your app with:
LazyMotion features={domMax} strict— enables all Framer Motion features with strict tree-shaking (useminstead ofmotion)MotionConfig reducedMotion="user"— respectsprefers-reduced-motion
3. Accent customization
Override the accent scale in your globals.css after importing the preset:
/* Example: Linkship uses violet */
@theme inline {
--color-accent-50: #f5f3ff;
--color-accent-100: #ede9fe;
--color-accent-200: #ddd6fe;
--color-accent-300: #c4b5fd;
--color-accent-400: #a78bfa;
--color-accent-500: #8B5CF6;
--color-accent-600: #7C3AED;
--color-accent-700: #6D28D9;
--color-accent-800: #5B21B6;
--color-accent-900: #4C1D95;
--color-accent-950: #2E1065;
}Pre-built presets are available in code:
import { ACCENT_PRESETS } from "@curetcore/ui/theme";
// ACCENT_PRESETS.violet, .cobalt, .lime, .emerald, .amberComponents
import { Button, Card, Input, Toggle, Modal, Badge } from "@curetcore/ui/components";| Component | Description |
|-----------|-------------|
| Button | 8 variants (primary, secondary, ghost, danger, destructive, pro, success, outline). Sizes xs-lg. Loading state with spinner. |
| Card | Glassmorphism card with 4 variants (default, nested, accent, danger). Accent border with 6 color options. |
| Input | Form input with label, error, prefix/suffix, icon, character count. Forwards ref to <input>. |
| Textarea | Same API as Input for multiline text. Forwards ref to <textarea>. |
| Toggle | Animated spring switch in 3 sizes (xs, sm, md). role="switch" + aria-checked. Requires aria-label. Forwards ref. |
| Modal | Confirmation dialog on native <dialog>. Typed-confirmation support, aria-modal, aria-describedby. i18n-ready labels. |
| ToastContainer | Auto-dismiss toast system with undo support. Pair with useToast() from the same import. |
| Badge | 7 variants: pro (animated shimmer), beta, status (dot indicator), count, founders, live (pulse), default. |
| Skeleton | 5 layout skeletons: SkeletonCard, SkeletonKPI, SkeletonChart, SkeletonList, SkeletonTable. |
| EmptyState | Icon + title + description + optional CTA button. |
| ErrorAlert | Inline error message with role="alert" and dismiss button. |
| LoadingButton | Button wrapper that shows loading text while submitting. |
| SafeImage | Next.js Image with automatic DiceBear Croodles fallback on error. |
Exported types
All component prop types are exported: ButtonProps, CardProps, CardAccentColor, InputProps, TextareaProps, ToggleProps, ModalProps, ToastContainerProps, BadgeProps, SkeletonProps, EmptyStateProps, ErrorAlertProps, LoadingButtonProps, SafeImageProps.
Hooks
import { useTheme, useDebounce, useCopyToClipboard } from "@curetcore/ui/hooks";useTheme
const { theme, setTheme, resolvedTheme, mounted } = useTheme();
// theme: "light" | "dark" | "system"
// resolvedTheme: "light" | "dark" (resolved from system preference)
// mounted: boolean (true after hydration — use to avoid flash)Persists to localStorage("theme"). Toggles .dark class on <html>. Listens to prefers-color-scheme changes when set to "system".
useDebounce
const debouncedSearch = useDebounce(searchQuery, 300);useCopyToClipboard
const { copy, copied } = useCopyToClipboard(2000); // reset after 2s
await copy("https://linkship.cc/ronaldo");
// copied === true for 2 secondsAlso exports the pure function copyToClipboard(text: string) for use outside React.
Utils
import { cn } from "@curetcore/ui/utils";
<div className={cn("px-4 rounded-xl", isActive && "bg-accent-500/10", className)} />cn() wraps clsx for conditional class merging. Every component in the package uses it internally.
Motion presets
import { springs, fadeSlideUp, staggerContainer, sectionReveal } from "@curetcore/ui/motion";Springs
Physics-based spring configs for Framer Motion transitions.
| Spring | Feel | Use case |
|--------|------|----------|
| springs.gentle | Ultra-soft, languid | Page transitions, large elements |
| springs.smooth | Default — natural | General-purpose, most components |
| springs.snappy | Quick, minimal overshoot | Toggles, buttons, small interactions |
| springs.bounce | Playful | Notifications, badges, celebrations |
| springs.floating | Very damped, glassy | Parallax, glassmorphism layers |
| springs.easeOutExpo | Fast out with decel | Dropdowns, menus, slide-overs |
Variants
| Variant | Description |
|---------|-------------|
| fadeSlideUp | Fade + slide from 30px below with blur |
| fadeSlideLeft | Fade + slide from 60px right with blur |
| fadeSlideRight | Fade + slide from 60px left with blur |
| scaleFade | Scale up from 0.85 + fade with blur |
| sectionReveal | Container for viewport-triggered stagger |
| REDUCED_MOTION_VARIANTS | Opacity-only fallback for accessibility |
staggerContainer(amount, delay)
Factory function that creates a Framer Motion Variants object for orchestrating child animations.
<m.div variants={staggerContainer(0.1)} initial="hidden" animate="visible">
<m.div variants={fadeSlideUp}>First</m.div>
<m.div variants={fadeSlideUp}>Second</m.div>
<m.div variants={fadeSlideUp}>Third</m.div>
</m.div>Theme tokens
import { BRAND_COLORS, ACCENT_PRESETS, EASINGS, SHADOWS } from "@curetcore/ui/theme";Brand colors
Fixed across all products — the Curetcore family palette:
| Token | Hex | Usage |
|-------|-----|-------|
| navy | #1E2330 | Dark backgrounds |
| navyDeep | #13141b | Deepest dark |
| cobalt | #3B42F0 | Primary brand blue |
| lime | #D2E823 | Highlight, CTA accent |
| sand | #EDE9DF | Warm neutral |
| cream | #F5F0E8 | Light warm background |
CSS utilities (from CSS files)
Glassmorphism: .glass, .glass-white
Shadows: .shadow-card, .shadow-card-hover, .shadow-float, .shadow-glow-lime
Hover: .hover-lift (translateY + scale on hover)
Scrollbars: .scrollbar-violet, .scrollbar-hide (accent-colored, dark mode aware)
Animations: .animate-float-up, .animate-shimmer, .animate-pulse-glow, .animate-breathe, .animate-smoke, .animate-aurora, .animate-in, .animate-card-shake, .animate-hero-zoom, .animate-profile-reveal, .animate-brand-fade, .animate-glow-breathe, .animate-marquee
Stagger patterns: .public-stagger > * (auto-stagger up to 10 children), .public-social-stagger > *
Reduced motion: All animations are disabled when prefers-reduced-motion: reduce is active.
CSS layers
The three CSS files form a layered system:
preset.css — Foundation. Theme tokens (
@theme inline), accent scale, typography (Outfit), dark mode via.darkclass, base body styles, selection color.animations.css — Motion. 18 keyframes, 20+ utility classes, stagger patterns, reduced motion override. All animations use the easing variables from the preset.
utilities.css — Effects. Glassmorphism, shadow presets, hover interactions, accent-colored scrollbars (WebKit + Firefox), scrollbar variants.
Versioning
This package follows strict Semantic Versioning:
| Type | When | Example |
|------|------|---------|
| MAJOR (X.0.0) | Breaking changes: required props added, APIs removed, behavior changes that break existing consumers | Toggle now requires aria-label |
| MINOR (0.X.0) | Backwards-compatible additions: new components, hooks, exports, optional props | Add useDebounce hook, add motion presets |
| PATCH (0.0.X) | Bug fixes, internal improvements, documentation, no API change | Fix build race condition, add displayName |
What counts as breaking
- Making an optional prop required
- Removing an exported component, hook, type, or function
- Changing the behavior of an existing prop in a way that breaks consumers
- Removing a CSS class or variable that consumers reference
- Changing the default value of a prop in a way that changes rendered output
What does NOT count as breaking
- Adding new optional props with sensible defaults
- Adding new components, hooks, or exports
- Adding new CSS keyframes or utility classes
- Internal refactoring that preserves the public API
- Improving accessibility without changing the API
Pre-release
For testing breaking changes before committing:
2.0.0-beta.1 → 2.0.0-beta.2 → 2.0.0-rc.1 → 2.0.0Contributing
Extraction rule
A component qualifies for @curetcore/ui when:
- It is used in 2 or more Curetcore products
- It is generic enough to work with just the accent system (no product-specific logic)
- It has been tested in production in at least one product
Process
- Identify the component in product A
- Confirm it is needed in product B
- Extract to
src/components/with generic props and accent-based styling - Add
displayName,forwardRef(for form elements), JSDoc, and i18n-ready string props - Export from
src/components/index.ts - Build, verify types, publish minor version
- Update both products to import from
@curetcore/ui/components
Component standards
- All components use
clsx()for class composition (never template literals) - All components have
displayName - Form components (
Input,Textarea,Toggle) forward refs - Interactive components have proper ARIA attributes
- Hardcoded strings are exposed as props with English defaults
- Springs are imported from
../motion, never hardcoded inline
Build
npm run build # rm -rf dist && tsup (two parallel configs)
npm run typecheck # tsc --noEmit
npm run dev # tsup --watchThe build uses two tsup configurations that run in parallel:
- Client config: components, hooks, provider, index — with
"use client"banner - Server config: theme, utils, motion — no banner
The dist/ directory is pre-cleaned in the build script to avoid race conditions between parallel configs.
Made by Ronaldo Paulino
