@maholan/cli
v0.2.0
Published
CLI for installing MHL UI components and design tokens
Readme
@maholan/cli
CLI for installing MHL UI components and design tokens. Inspired by shadcn/ui's component registry pattern.
Features
- 🎨 Interactive Setup - Choose your theme (brand color, gray base, radius style)
- 📦 Component Registry - Install components on-demand from registry
- 🔧 Smart Configuration - Auto-detects framework (Next.js, Vite, Remix)
- 🎯 Dependency Resolution - Automatically installs component dependencies
- 🌈 Theme Variants - 300+ theme combinations (10 brands × 6 grays × 5 radius)
- 🔄 Diff Checking - Compare local files with registry versions
- 🚀 Package Manager Detection - Works with npm, yarn, pnpm, bun
Installation
# Using npx (recommended)
npx @maholan/cli init
# Or install globally
npm install -g @maholan/cli
mhl init
# With other package managers
pnpm dlx @maholan/cli init
yarn dlx @maholan/cli init
bunx @maholan/cli initUsage
Initialize Project
npx @maholan/cli initThis will:
- Detect your framework (Next.js App Router, Pages Router, Vite, Remix)
- Prompt you to select a brand color from 10 options (purple, blue, cerulean, teal, indigo, fuchsia, pink, magenta, orange, rose)
- Configure component and utility paths
- Create
mhl.config.jsonconfiguration file - Install base dependencies (
@maholan/tokens,@maholan/theme,react-aria-components,class-variance-authority,clsx,tailwind-merge) - Inject
@import "@maholan/tokens/mhl-tokens.css"into your global CSS file - Create
cn()utility function
Add Components
# Add single component
npx @maholan/cli add button
# Add multiple components
npx @maholan/cli add button input dialog
# Add all components
npx @maholan/cli add --all
# Interactive selection
npx @maholan/cli add
# Skip confirmation prompts
npx @maholan/cli add button --yes
# Overwrite existing files
npx @maholan/cli add button --overwrite
# Install with custom theme variant (NEW! 🎨)
npx @maholan/cli add button --variant
# Install with Storybook stories (NEW! 📚)
npx @maholan/cli add button --with-stories
# Combine flags
npx @maholan/cli add button --variant --with-storiesTheme Variant Installation
The --variant flag allows you to install components with custom theme
settings:
# Install button with custom theme
npx @maholan/cli add button --variant
# Interactive prompts:
? Change brand color? (current: purple) › Yes
? Select brand color: › blue
? Change gray base? (current: gray) › No
? Change border radius? (current: md) › Yes
? Select border radius: › lg (12px)
📦 Installing with custom theme:
Brand: blue
Gray: gray
Radius: lg
✅ Component installed with custom theme variant!Use Cases:
- 🎨 Multi-brand monorepos - Different themes per app
- 🧪 A/B testing - Test design variants
- 👥 Client customization - Client-specific themes
- 🔄 Progressive migration - Test new theme on specific components
Features:
- ✅ 300+ theme combinations (10 brands × 6 grays × 5 radius)
- ✅ Automatic kebab-case file naming
- ✅ Theme variant comments in generated files
- ✅ Compatible with standard installation
Storybook Stories Installation
The --with-stories flag installs Storybook stories alongside components:
# Install button with stories
npx @maholan/cli add button --with-stories
# Install multiple components with stories
npx @maholan/cli add button input dialog --with-stories
# Combine with theme variant
npx @maholan/cli add button --variant --with-storiesWhat gets installed:
- Component files:
button.tsx,button.variants.ts - Story file:
button.stories.tsx(in the same directory) - Storybook dev dependencies (if not already installed)
File structure:
src/components/ui/
└── button/
├── button.tsx
├── button.variants.ts
└── button.stories.tsx ← Storybook storyBenefits:
- 📚 Interactive component documentation
- 🎨 Visual testing in isolation
- 🧪 Easy variant showcase
- 📝 Usage examples
- 🔍 Props documentation via autodocs
When you add a component:
- Fetches component from registry
- Resolves registry dependencies (e.g., dialog depends on button)
- Installs npm dependencies automatically
- Transforms imports to match your project aliases
- Converts file names to kebab-case (Button.tsx → button.tsx)
- Applies theme variant if --variant flag is used
- Copies files to configured paths
Check for Updates
# Check specific component
npx @maholan/cli diff button
# See which files have changedOutput:
✓ OK src/components/ui/button/button.tsx
⚠ MODIFIED src/components/ui/button/button.variants.tsSync Token CSS
Keep your local mhl-tokens.css in sync with the latest registry version:
# Re-fetch mhl-tokens.css and overwrite the local copy
npx @maholan/cli sync
# CI mode — exits 1 if local file is outdated, does not write
npx @maholan/cli sync --checkOutput:
✓ mhl-tokens.css is up to date (0.2.1)
# or when outdated:
⚠ mhl-tokens.css is outdated (local: 0.2.1, registry: 0.3.0)
Run `mhl sync` to update.The local file location and version are stored in mhl.config.json:
{
"tokens": {
"css": "src/styles/mhl-tokens.css",
"version": "0.2.1"
}
}Configuration
After running init, a mhl.config.json file is created:
{
"$schema": "https://mhl-ui.dev/schema/config.json",
"framework": "next-app",
"typescript": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css"
},
"aliases": {
"components": "@/components/ui",
"utils": "@/lib/utils"
},
"theme": {
"brand": "purple",
"gray": "neutral",
"radius": "base"
},
"registries": {
"@maholan": "https://mhl-ui.dev/r"
}
}Theme Variants
Brand Colors (10 options)
| Key | Description |
| ---------- | ------------------------ |
| purple | MHL default |
| blue | Professional, enterprise |
| cerulean | Fresh, modern |
| teal | Calm, balanced |
| indigo | Deep, sophisticated |
| fuchsia | Bold, vibrant |
| pink | Playful, energetic |
| magenta | Creative, artistic |
| orange | Warm, friendly |
| rose | Elegant, refined |
Gray Bases (6 options)
| Key | Tone | Description |
| --------- | -------- | ------------------------------------ |
| neutral | Warm | Default — slightly warm gray |
| steel | Cool | Cool gray (used for dark mode bases) |
| slate | Cool | Blue-tinted gray |
| zinc | Neutral | True neutral gray |
| stone | Warm | Warm brown-gray |
| gray | Balanced | Balanced gray |
Border Radius (5 options)
| Key | Value | Use Case |
| ---------- | ----- | ---------------------- |
| base | 8px | Balanced (default) |
| sharp | 0px | Brutalist, modern |
| square | 4px | Subtle roundness |
| round | 16px | Friendly, approachable |
| circular | pill | Maximum rounding |
Component Registry
Components are distributed via a JSON registry (similar to shadcn/ui):
Registry Structure
https://mhl-ui.dev/r/
├── index.json # List of all components
├── button.json # Button component
├── input.json # Input component
├── dialog.json # Dialog component
└── ...Registry Item Example
{
"name": "button",
"type": "registry:component",
"description": "Accessible button built on React Aria",
"dependencies": [
"react-aria-components",
"class-variance-authority",
"clsx",
"tailwind-merge"
],
"registryDependencies": [],
"files": [
{
"path": "registry/mhl/button/button.tsx",
"content": "...",
"type": "registry:component"
},
{
"path": "registry/mhl/button/button.variants.ts",
"content": "...",
"type": "registry:component"
}
],
"cssVars": {
"light": {
"brand-600": "257 90% 58%"
},
"dark": {
"brand-600": "257 90% 65%"
}
}
}How It Works
1. Initialization Flow
┌─────────────────────────────────────────┐
│ npx @maholan/cli init │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Detect Framework (Next.js, Vite, etc) │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Interactive Prompts: │
│ - Brand Color (10 options) │
│ - Component Path │
│ - Utils Path │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Create mhl.config.json │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Install Base Dependencies │
│ (@maholan/tokens, @maholan/theme, │
│ react-aria-components, CVA, clsx, │
│ tailwind-merge) │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Inject CSS into globals.css: │
│ @import "@maholan/tokens/mhl-tokens" │
│ @import "@maholan/theme/global" │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Create Directories & cn() Utility │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ ✅ Ready to add components! │
└─────────────────────────────────────────┘2. Component Installation Flow
┌─────────────────────────────────────────┐
│ npx @maholan/cli add button │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Fetch button.json from Registry │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Resolve Registry Dependencies │
│ (e.g., dialog needs button) │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Install npm Dependencies │
│ (react-aria-components, etc.) │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Transform Imports │
│ (@/components/ui → your alias) │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Copy Files to Project │
│ (src/components/ui/button/...) │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ ✅ Component ready to import! │
└─────────────────────────────────────────┘Architecture
This CLI follows the shadcn/ui pattern:
- Registry-Based - Components stored as JSON with source code
- Copy, Not Install - Files copied to your project (you own the code)
- Dependency Resolution - Automatically handles component dependencies
- Import Transformation - Adjusts imports to match your project structure
- Theme Variants - Select design tokens during initialization
Commands Reference
| Command | Description | Example |
| --------------------- | ---------------------------- | ---------------------------- |
| init | Initialize MHL UI in project | mhl init |
| add [components...] | Add components | mhl add button input |
| add --all | Install all components | mhl add --all |
| add --yes | Skip confirmation prompts | mhl add button --yes |
| add --overwrite | Overwrite existing files | mhl add button --overwrite |
| diff [component] | Check for changes | mhl diff button |
| sync | Update local mhl-tokens.css | mhl sync |
| sync --check | CI check (exits 1 if stale) | mhl sync --check |
Examples
Example 1: New Next.js App Router Project
# Initialize
npx @maholan/cli init
# Select theme:
# Brand: Purple
# Gray: Gray
# Radius: Medium
# Add components
npx @maholan/cli add button input dialog
# Use in your app
import { Button } from "@/components/ui/button";Example 2: Existing Vite Project
# Initialize with custom paths
npx @maholan/cli init
# Select:
# Components: src/components
# Utils: src/lib
# Add all components
npx @maholan/cli add --all --yesExample 3: Check for Updates
# Check if components need updating
npx @maholan/cli diff button
npx @maholan/cli diff input
# Update if needed
npx @maholan/cli add button --overwriteComparison with shadcn/ui
| Feature | shadcn/ui | @maholan/cli | | ------------------------- | --------- | -------------------- | | Registry Pattern | ✅ | ✅ | | Component Copy | ✅ | ✅ | | Theme Variants | Limited | ✅ 300+ combinations | | Design Tokens | Basic | ✅ 4-tier system | | Framework Detection | ✅ | ✅ | | Package Manager Detection | ✅ | ✅ | | Dependency Resolution | ✅ | ✅ | | React Aria Foundation | ❌ | ✅ | | MHL Design System | ❌ | ✅ |
Troubleshooting
CLI not found
# Use npx instead
npx @maholan/cli init
# Or install globally
npm install -g @maholan/cliImport errors
Check your tsconfig.json has correct path aliases:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}Component not found
Verify registry URL in mhl.config.json:
{
"registries": {
"@maholan": "https://mhl-ui.dev/r"
}
}Related Packages
@maholan/tokens- Design token system (Figma → TypeScript)@maholan/ui- Pre-built component library (npm install model)@maholan/theme- Theme provider and global styles
License
MIT © MHL Platform
