npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@projectdiscoveryio/design-system

v1.0.2

Published

Production-grade design system with adapter layer support

Readme

@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

Install the package using npm, yarn, or pnpm:

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+:

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):

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

Step 2: Wrap Your App with ThemeProvider

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

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:

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:

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:

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) | | endIcon | string | - | Icon name to show after text (must be a valid Lucide icon) | | 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:

// 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:

<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.

<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.

<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.

<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:

// 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:

// 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:

// 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:

// 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:

<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:

.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:

// 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:

// 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:

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

3. Dark Mode

Always uses dark mode, ignoring system preference:

<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:

// 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:

<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:

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

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/

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

// 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:

<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:

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)

<ThemeProvider>
  <App />
</ThemeProvider>

What you get:

  • Default adapter (shadcn)
  • Default theme (base)
  • System mode detection (follows OS)
  • Zero configuration

Optional: Add Persistence

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

Additional benefits:

  • Theme persistence (remembers user choice)
  • Cross-tab sync

Use Case 2: Force Light Mode

<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

<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

// 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:

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:

<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