npm package: @projectdiscoveryio/design-system
Description: Production-grade design system with adapter layer support
Author: ProjectDiscovery
Latest version: 1.0.2

# @pd-design/system

Production-grade design system with adapter layer support. Built for teams who need a flexible, type-safe component library that can work with different UI engines without changing application code.

## About PD Design System

- **3 Lines to Get Started** - No complex setup, just works
- **System Mode Built-In** - Automatically follows OS dark/light preference
- **Zero-Config Persistence** - Pass `storageKey` prop, localStorage sync is automatic
- **Cross-Tab Sync** - Theme changes sync across browser tabs instantly
- **Type-Safe** - Full TypeScript with autocomplete for all props
- **Accessible** - WCAG 2.1 AA compliant, built on Radix UI
- **Style Isolation** - Works with Tailwind, MUI, AntD, Bootstrap, or plain CSS
- **Extensible Adapters** - Switch between UI engines (shadcn, Material, AntD, etc.)
- **Flexible Theming** - Base theme with support for custom brand themes
- **1000+ Icons** - All Lucide icons with TypeScript autocomplete

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Implementation Guide](#implementation-guide)
- [Design System Architecture](#design-system-architecture)
- [Adapters](#adapters)
- [Theming System](#theming-system)
- [Mode System](#mode-system)
- [Component Usage](#component-usage)
- [API Reference](#api-reference)
- [Advanced Usage](#advanced-usage)

## Installation

Install the package using npm, yarn, or pnpm:

```bash
npm install @pd-design/system
# or
yarn add @pd-design/system
# or
pnpm add @pd-design/system
```

### Peer Dependencies

The design system requires React 18+:

```bash
npm install react@^18.0.0 react-dom@^18.0.0
```

## Quick Start

Get started in 3 simple steps:

### Step 1: Import Styles

Import the design system styles in your application entry point (e.g., `main.tsx`, `App.tsx`, or `_app.tsx`):

```tsx
import '@pd-design/system/styles.css';
```

### Step 2: Wrap Your App with ThemeProvider

Wrap your application with `ThemeProvider` to enable theming and component functionality:

```tsx
import { ThemeProvider } from '@pd-design/system';

function App() {
  return (
    <ThemeProvider>
      {/* Your application components */}
    </ThemeProvider>
  );
}
```

**That's it!** The `ThemeProvider` uses sensible defaults:
- **Adapter**: `'shadcn'` (Radix UI + Tailwind CSS)
- **Theme**: `'base'` (Neutral, professional design)
- **Mode**: `'system'` (Automatically follows OS dark/light preference)

#### Optional Configuration

You can customize the theme, mode, adapter, and enable persistence:

```tsx
import { ThemeProvider } from '@pd-design/system';

function App() {
  return (
    <ThemeProvider 
      adapter="shadcn"           // Optional: 'shadcn' | 'material' (default: 'shadcn')
      theme="base"               // Optional: 'base' | 'brand' (default: 'base')
      mode="system"              // Optional: 'light' | 'dark' | 'system' (default: 'system')
      storageKey="my-app-theme"  // Optional: enables localStorage persistence
    >
      {/* Your application components */}
    </ThemeProvider>
  );
}
```

### Step 3: Use Components

Now you can use any component from the design system:

```tsx
import { Button } from '@pd-design/system';

function MyComponent() {
  return (
    <Button>
      Click me
    </Button>
  );
}
```

That's it! 🎉 The `ThemeProvider` automatically handles:
- **System preference detection** - Follows OS dark/light mode
- **localStorage persistence** - Remembers user's theme choice (when `storageKey` is provided)
- **Cross-tab sync** - Theme changes sync across browser tabs
- **Class management** - Automatically adds `pd-root` and `pd-dark` classes
- **CSS variables** - Sets all theme variables automatically

## Implementation Guide

### Complete Example: Using Button Component

Here's a complete example showing how to set up and use the Button component:

```tsx
import { ThemeProvider, Button, useTheme } from '@pd-design/system';
import '@pd-design/system/styles.css';

function App() {
  return (
    <ThemeProvider>
      <MyComponent />
    </ThemeProvider>
  );
}

function MyComponent() {
  const { config, setConfig } = useTheme();
  
  return (
    <div>
      {/* Basic Button */}
      <Button variant="primary" size="md">
        Primary Button
      </Button>
      
      {/* Button with Icons (must be valid Lucide icons - see https://lucide.dev/icons/) */}
      <Button 
        variant="secondary" 
        size="lg"
        startIcon="Download"
        endIcon="ArrowRight"
      >
        Download File
      </Button>
      
      {/* Button States */}
      <Button variant="primary" loading={true}>
        Loading...
      </Button>
      
      <Button variant="primary" disabled={true}>
        Disabled
      </Button>
      
      {/* Button Variants */}
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="destructive">Delete</Button>
      
      {/* Button Sizes */}
      <Button size="sm">Small</Button>
      <Button size="md">Medium</Button>
      <Button size="lg">Large</Button>
      
      {/* Theme Toggle Example */}
      <Button onClick={() => {
        const nextMode = config.mode === 'dark' ? 'light' : 'dark';
        setConfig({ mode: nextMode });
      }}>
        Switch to {config.mode === 'dark' ? 'Light' : 'Dark'} Mode
      </Button>
    </div>
  );
}
```

### Button Component API

The Button component accepts the following props:

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'primary' \| 'secondary' \| 'ghost' \| 'destructive'` | `'primary'` | Visual style variant |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
| `loading` | `boolean` | `false` | Shows loading state |
| `disabled` | `boolean` | `false` | Disables the button |
| `fullWidth` | `boolean` | `false` | Makes button full width |
| `startIcon` | `string` | `-` | Icon name to show before text (must be a valid [Lucide icon](https://lucide.dev/icons/)) |
| `endIcon` | `string` | `-` | Icon name to show after text (must be a valid [Lucide icon](https://lucide.dev/icons/)) |
| `loadingText` | `string` | `-` | Text to show during loading state |
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | Button type for forms |
| `href` | `string` | `-` | Renders as link when provided |
| `target` | `'_blank' \| '_self' \| '_parent' \| '_top'` | `undefined` | Link target (when href is provided) |
| `asChild` | `boolean` | `false` | Render as child element (Radix UI pattern) |

**Example Usage:**

```tsx
// Form submission button
<form onSubmit={handleSubmit}>
  <Button type="submit" variant="primary">
    Submit Form
  </Button>
  <Button type="reset" variant="secondary">
    Reset
  </Button>
</form>

// Button with icons (must be valid Lucide icons - see https://lucide.dev/icons/)
<Button startIcon="Download" variant="primary">
  Download
</Button>

<Button endIcon="ArrowRight" variant="secondary">
  Next Step
</Button>

// Loading state
<Button loading={isLoading} loadingText="Processing...">
  Save Changes
</Button>

// Link button
<Button href="/dashboard" variant="ghost">
  Go to Dashboard
</Button>
```

## Design System Architecture

The PD Design System follows a **headless-first, adapter-based architecture**:

```
┌─────────────────────────────────────────────────────────┐
│                    Your Application                      │
│  (Uses components with props-only API, no className)    │
└──────────────────────┬──────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────────┐
│              Design System Components                    │
│  (Button, Input, Select, etc. - Type-safe props API)    │
└──────────────────────┬──────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────────┐
│                  Adapter Layer                           │
│  (shadcn, Material, AntD, etc. - Switchable engines)    │
└──────────────────────┬──────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────────┐
│              UI Engine Primitives                        │
│  (Radix UI, Material UI, Ant Design, etc.)               │
└─────────────────────────────────────────────────────────┘
```

### Key Principles

1. **Props-only API**: Consumers use only props and variants, no `className` required
2. **Full TypeScript**: Complete autocomplete and type safety for all props
3. **Adapter Layer**: Switch between UI engines without changing application code
4. **Semantic Tokens**: Design tokens that work across themes and adapters
5. **Framework Agnostic**: Design tokens and semantics are framework-agnostic
6. **Zero-Config Theming**: Automatic system detection and localStorage sync
7. **Style Isolation**: Scoped styles prevent conflicts with consumer CSS

### Style Isolation

The design system uses **scoped styling** to prevent style conflicts:

- **All Tailwind classes are prefixed with `pd-`** - No collision with consumer Tailwind
- **All CSS variables are scoped under `.pd-root`** - No token leakage
- **Preflight is disabled** - Consumer styles are never reset
- **Works with any consumer setup** - Tailwind, MUI, AntD, Bootstrap, or plain CSS

**Always wrap your components with `ThemeProvider`** to create the scoped boundary:

```tsx
<ThemeProvider storageKey="my-theme">
  {/* Your design system components */}
</ThemeProvider>
```

The `ThemeProvider` automatically adds the `pd-root` class to your body element, so all tokens are properly scoped.

## Adapters

The adapter layer allows you to switch between different UI engines without changing your application code. This provides flexibility to use the underlying UI library that best fits your needs.

### Supported Adapters

#### 1. Shadcn Adapter (Default)

Built on Radix UI primitives and Tailwind CSS. This is the default adapter.

```tsx
<ThemeProvider adapter="shadcn">
  <Button variant="primary">Click me</Button>
</ThemeProvider>
```

**Features:**
- Radix UI primitives for accessibility
- Tailwind CSS for styling
- Fully customizable via design tokens
- Zero runtime dependencies for styling

#### 2. Material Adapter (Coming Soon)

Built on Material UI components.

```tsx
<ThemeProvider adapter="material">
  <Button variant="primary">Click me</Button>
</ThemeProvider>
```

**Note:** Material adapter is planned for future releases. When available, you can switch adapters without changing your component code.

#### 3. Ant Design Adapter (Planned)

Built on Ant Design components.

```tsx
<ThemeProvider adapter="antd">
  <Button variant="primary">Click me</Button>
</ThemeProvider>
```

**Note:** Ant Design adapter is planned for future releases.

### Switching Adapters

You can switch adapters at any time without changing your component code:

```tsx
// Switch from shadcn to material (when available)
<ThemeProvider adapter="material" theme="base" mode="system">
  <Button variant="primary">Same Button, Different Engine</Button>
</ThemeProvider>
```

The component API remains the same regardless of the adapter used. This allows you to:
- Test different UI engines
- Migrate between engines gradually
- Use different engines for different parts of your application
- Choose the engine that best fits your performance and bundle size requirements

### Adapter Configuration

By default, the `shadcn` adapter is used. You can optionally specify a different adapter:

```tsx
// Default (no adapter prop needed)
<ThemeProvider>
  <Button variant="primary">Click me</Button>
</ThemeProvider>

// Optional: Specify adapter explicitly
<ThemeProvider 
  adapter="shadcn"  // Optional: 'shadcn' | 'material' | 'antd' (when available)
>
  <Button variant="primary">Click me</Button>
</ThemeProvider>
```

**Default:** `'shadcn'`

## Theming System

The design system supports **themes** and **modes** as separate concepts:

- **Theme** (`theme`): The design theme name - defines brand colors and styling
- **Mode** (`mode`): The color scheme - light, dark, or system preference

### Themes

Themes define the overall design language, including brand colors, typography, and styling preferences.

#### Base Theme (Default)

The `base` theme provides a neutral, professional design suitable for most applications. This is the default theme:

```tsx
// Default (no theme prop needed)
<ThemeProvider>
  <Button variant="primary">Base Theme Button</Button>
</ThemeProvider>
```

#### Brand Theme (Optional)

The `brand` theme extends the base theme with custom brand colors:

```tsx
// Optional: Use brand theme
<ThemeProvider theme="brand">
  <Button variant="primary">Brand Theme Button</Button>
</ThemeProvider>
```

**Note:** Brand theme customization is available for extending with your own brand colors.

### Custom Theme Extension

You can extend themes with custom brand colors using CSS variables:

```tsx
<ThemeProvider 
  theme="base" 
  mode="light"
  className="pd-root [--pd-primary:220_80%_50%]"
>
  {/* Custom primary color */}
  <Button variant="primary">Custom Brand Color</Button>
</ThemeProvider>
```

Or via CSS:

```css
.pd-root {
  --pd-primary: 220 80% 50%;
  --pd-primary-foreground: 0 0% 98%;
  /* Add more custom tokens as needed */
}
```

### Theme Configuration

By default, the `base` theme is used. You can optionally specify a different theme:

```tsx
// Default (no theme prop needed)
<ThemeProvider>
  <Button variant="primary">Base Theme</Button>
</ThemeProvider>

// Optional: Use brand theme
<ThemeProvider theme="brand">
  <Button variant="primary">Brand Theme</Button>
</ThemeProvider>
```

**Default:** `'base'`

## Mode System

The mode system controls the color scheme (light/dark) of your application. It supports three modes:

### 1. System Mode (Default)

Automatically follows the OS dark/light preference. This is the default mode:

```tsx
// Default (no mode prop needed)
<ThemeProvider>
  {/* Automatically follows OS preference */}
  <Button variant="primary">System Mode</Button>
</ThemeProvider>
```

**Behavior:**
- Detects OS preference on mount
- Updates automatically when OS preference changes
- Works seamlessly with localStorage persistence
- Recommended for most applications

### 2. Light Mode

Always uses light mode, ignoring system preference:

```tsx
<ThemeProvider mode="light">
  <Button variant="primary">Always Light</Button>
</ThemeProvider>
```

### 3. Dark Mode

Always uses dark mode, ignoring system preference:

```tsx
<ThemeProvider mode="dark">
  <Button variant="primary">Always Dark</Button>
</ThemeProvider>
```

### Mode Configuration

By default, `system` mode is used. You can optionally specify a different mode:

```tsx
// Default (no mode prop needed - follows OS preference)
<ThemeProvider>
  <Button variant="primary">System Mode</Button>
</ThemeProvider>

// Optional: Force light mode
<ThemeProvider mode="light">
  <Button variant="primary">Always Light</Button>
</ThemeProvider>

// Optional: Force dark mode
<ThemeProvider mode="dark">
  <Button variant="primary">Always Dark</Button>
</ThemeProvider>
```

**Default:** `'system'`

### localStorage Persistence

Pass `storageKey` to enable automatic theme persistence:

```tsx
<ThemeProvider storageKey="my-app-theme">
  {/* 
    Reads saved preference on mount
    Saves changes automatically
    Syncs across browser tabs
    Works with system mode
  */}
</ThemeProvider>
```

**What gets saved:**
- User's manual selection (`'light'` | `'dark'`)
- `'system'` preference (so it knows to follow OS)

**Example flow:**
1. User selects "Dark" → Saved to localStorage
2. User refreshes page → Reads "Dark" from localStorage
3. User opens new tab → Reads "Dark" from localStorage (synced)
4. User changes to "System" → Saved, now follows OS preference

### Theme Toggle Component

Here's an example of how to create a theme toggle:

```tsx
import { useTheme, Button } from '@pd-design/system';

function ThemeToggle() {
  const { config, setConfig } = useTheme();
  
  const toggle = () => {
    const modes = ['light', 'dark', 'system'] as const;
    const currentIndex = modes.indexOf(config.mode);
    const nextIndex = (currentIndex + 1) % modes.length;
    setConfig({ mode: modes[nextIndex] });
  };
  
  return (
    <Button onClick={toggle} variant="ghost">
      {config.mode === 'system' && '🌓 System'}
      {config.mode === 'light' && '☀️ Light'}
      {config.mode === 'dark' && '🌙 Dark'}
    </Button>
  );
}
```

## Component Usage

### Available Components

The design system provides a growing set of components. Here are some examples:

#### Button

```tsx
import { Button } from '@pd-design/system';

<Button variant="primary" size="md">Click me</Button>
<Button variant="secondary" startIcon="Download">Download</Button>
<Button variant="destructive" loading={true}>Delete</Button>
```

Browse all available icons: [https://lucide.dev/icons/](https://lucide.dev/icons/)

### Component Variants

All components follow a consistent variant system:

- **variant**: `primary` | `secondary` | `ghost` | `destructive`
- **size**: `sm` | `md` | `lg`
- **state**: `loading` | `disabled`

No arbitrary strings or raw class overrides are allowed. This ensures consistency and type safety.

## API Reference

### ThemeProvider Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `adapter` | `'shadcn' \| 'material'` | `'shadcn'` | UI engine adapter |
| `theme` | `'base' \| 'brand'` | `'base'` | Theme name |
| `mode` | `'light' \| 'dark' \| 'system'` | `'system'` | Color mode (system follows OS) |
| `storageKey` | `string?` | `undefined` | localStorage key for persistence (enables sync) |
| `children` | `ReactNode` | Required | Your application components |

### Exports

```tsx
// Components
import { 
  Button, 
  Input,
  Select,
  Checkbox,
  Radio,
  Switch,
  Modal,
  Tooltip,
  Dropdown,
  Toast,
  PdThemeProvider,
  Icons 
} from '@pd-design/system';

// Theme (Recommended: ThemeProvider with storageKey)
import { 
  ThemeProvider,      // Full-featured provider with localStorage sync
  useTheme,           // Hook to access/update theme
  setDesignSystemConfig,
  getDesignSystemConfig 
} from '@pd-design/system';

// Types
import type { 
  Variant, 
  Size, 
  ButtonType,
  ThemeName,
  ThemeMode,
  AdapterType,
  LucideIconName 
} from '@pd-design/system';

// Styles (required)
import '@pd-design/system/styles.css';
```

## Advanced Usage

### Per-Widget Theming

You can wrap only part of your page with `PdThemeProvider` for widget-level theming:

```tsx
<div>
  {/* Consumer styles */}
  <PdThemeProvider theme="base" mode="dark">
    {/* Design system widget with dark theme */}
    <Button variant="primary">Dark Widget</Button>
  </PdThemeProvider>
</div>
```

### Manual Configuration

For advanced use cases, you can configure the design system globally:

```tsx
import { setDesignSystemConfig } from '@pd-design/system';

// Set adapter, theme, and mode
setDesignSystemConfig({
  adapter: 'shadcn',
  theme: 'base',
  mode: 'light',
});
```

**Note:** When using `ThemeProvider` with `storageKey`, you don't need to call `setDesignSystemConfig` manually - it's handled automatically.

### PdThemeProvider vs ThemeProvider

- **`PdThemeProvider`**: Lightweight wrapper that creates the `.pd-root` scoped boundary. Use for simple theming needs without context.
- **`ThemeProvider`**: Full-featured provider with React context, system preference detection, localStorage sync, and CSS variable management. **Recommended for most use cases.**

Both support the same `theme` and `mode` props.

## Common Use Cases

### Use Case 1: Basic Setup (Default)

```tsx
<ThemeProvider>
  <App />
</ThemeProvider>
```

**What you get:**
- Default adapter (`shadcn`)
- Default theme (`base`)
- System mode detection (follows OS)
- Zero configuration

**Optional: Add Persistence**

```tsx
<ThemeProvider storageKey="my-app-theme">
  <App />
</ThemeProvider>
```

**Additional benefits:**
- Theme persistence (remembers user choice)
- Cross-tab sync

### Use Case 2: Force Light Mode

```tsx
<ThemeProvider mode="light">
  <App />
</ThemeProvider>
```

**What you get:**
- Always light mode
- Ignores system preference
- No localStorage (no storageKey)

### Use Case 3: Custom Theme with System Mode

```tsx
<ThemeProvider 
  theme="brand" 
  storageKey="my-brand-theme"
>
  <App />
</ThemeProvider>
```

**What you get:**
- Brand theme
- System mode with persistence
- User can override system preference

### Use Case 4: Different Adapters

```tsx
// Main app with shadcn
<ThemeProvider adapter="shadcn" storageKey="my-theme">
  <MainApp />
</ThemeProvider>

// Widget with material (when available)
<PdThemeProvider adapter="material" theme="base" mode="dark">
  <Widget />
</PdThemeProvider>
```

## Browser Support

- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Mobile browsers (iOS Safari, Chrome Mobile)

## Storybook

View all components and variants in Storybook:

```bash
npm run storybook
```

Storybook includes:
- All component variants with live examples
- Interactive controls for all props
- **Theme switcher** (base/brand) - See components in different themes
- **Mode switcher** (light/dark/system) - Test system mode detection
- **Adapter switcher** (shadcn/material) - Switch UI engines
- **Icons browser** with search - Browse 1000+ Lucide icons
- **Accessibility panel** - WCAG compliance checks
- **Code snippets** - Copy-paste ready examples
- **Design tokens showcase** - See all color primitives and semantic tokens

## TypeScript

Full TypeScript support with autocomplete for all props:

```tsx
<Button
  variant="primary"      // Autocomplete: primary | secondary | ghost | destructive
  size="md"              // Autocomplete: sm | md | lg
  startIcon="Download"   // Autocomplete: All Lucide icon names
  endIcon="ArrowRight"   // Autocomplete: All Lucide icon names
/>

<ThemeProvider
  theme="base"           // Autocomplete: base | brand
  mode="system"          // Autocomplete: light | dark | system
  adapter="shadcn"       // Autocomplete: shadcn | material
/>
```

## Accessibility

- Radix UI primitives for ARIA compliance
- Keyboard navigation support (Tab, Enter, Space, Arrow keys)
- Focus ring standards (visible focus indicators)
- Screen reader labels (aria-label, aria-describedby)
- Contrast-safe tokens (WCAG AA compliant)
- Disabled state handling (aria-disabled, pointer-events)
- Loading state indicators (aria-busy)

## Contributing

This is an internal design system. For contributions, please follow the architecture guidelines in `ARCHITECTURE.md`.

## License

MIT