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

@skylabs-digital/react-proto-kit

v1.35.0

Published

React prototyping kit for rapid API development - from idea to working prototype in minutes

Readme

React Proto Kit

From idea to working prototype in minutes

A powerful React prototyping toolkit that eliminates boilerplate and accelerates development. Build full-stack applications with type-safe APIs, real-time state management, and automatic CRUD operations.

npm version TypeScript React Zod

🚀 Quick Start

One-liner to get started:

npm install @skylabs-digital/react-proto-kit zod react react-router-dom
import { createDomainApi, z } from '@skylabs-digital/react-proto-kit';

// Define your data schema
const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().min(0),
});

// Create a fully functional API
const userApi = createDomainApi('users', userSchema, userSchema);

// Use it in your component
function UserList() {
  const { data: users, loading } = userApi.useList();
  const { mutate: createUser } = userApi.useCreate();
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <div>
      <button onClick={() => createUser({ name: 'John', email: '[email protected]', age: 30 })}>
        Add User
      </button>
      {users?.map(user => (
        <div key={user.id}>{user.name} - {user.email}</div>
      ))}
    </div>
  );
}

That's it! You now have a fully functional CRUD API with TypeScript support, optimistic updates, and automatic state management.

✨ Key Features

  • 🔥 Zero Boilerplate: One function call creates a complete CRUD API
  • 🎯 Type-Safe: Full TypeScript support with automatic type inference
  • ⚡ Real-time: Automatic state synchronization across components
  • 🔄 Optimistic Updates: Instant UI feedback with automatic rollback on errors
  • 🌐 Backend Agnostic: Works with any REST API or local storage
  • 📝 Form Handling: Built-in form validation and state management
  • 🔗 Nested Resources: Support for complex resource relationships
  • 🎨 Builder Pattern: Chainable API for dynamic configurations
  • 📊 Query Parameters: Static and dynamic query parameter management
  • 🔍 URL State: Automatic URL synchronization for filters and pagination
  • 🎭 Data Orchestrator: Aggregate multiple API calls with smart loading states
  • ⚡ Auto-Refetch: Watch URL params and automatically refetch data on changes
  • ✨ Smooth Transitions: Stale-while-revalidate for flicker-free UX
  • 🎨 UI Components: Built-in Modal, Drawer, Tabs, Stepper, Accordion, and Snackbar components with URL state management

📖 Table of Contents

📦 Installation

# npm
npm install @skylabs-digital/react-proto-kit zod react react-router-dom

# yarn
yarn add @skylabs-digital/react-proto-kit zod react react-router-dom

# pnpm
pnpm add @skylabs-digital/react-proto-kit zod react react-router-dom

Peer Dependencies

  • react >= 16.8.0
  • react-router-dom >= 6.0.0
  • zod >= 3.0.0

🎯 Basic Usage

1. Setup Providers

Wrap your app with the necessary providers:

import { BrowserRouter } from 'react-router-dom';
import { ApiClientProvider, GlobalStateProvider } from '@skylabs-digital/react-proto-kit';

function App() {
  return (
    <BrowserRouter>
      <ApiClientProvider connectorType="fetch" config={{ baseUrl: 'http://localhost:3001' }}>
        <GlobalStateProvider>
          {/* Your app components */}
        </GlobalStateProvider>
      </ApiClientProvider>
    </BrowserRouter>
  );
}

2. Define Your Schema

import { z } from '@skylabs-digital/react-proto-kit';

const todoSchema = z.object({
  text: z.string().min(1, 'Todo text is required'),
  completed: z.boolean(),
  priority: z.enum(['low', 'medium', 'high']).default('medium'),
});

3. Create Your API

import { createDomainApi } from '@skylabs-digital/react-proto-kit';

const todoApi = createDomainApi('todos', todoSchema, todoSchema, {
  optimistic: true,
  cacheTime: 5 * 60 * 1000, // 5 minutes
});

4. Use in Components

function TodoApp() {
  const { data: todos, loading, error } = todoApi.useList();
  const { mutate: createTodo } = todoApi.useCreate();
  const { mutate: updateTodo } = todoApi.useUpdate();
  const { mutate: deleteTodo } = todoApi.useDelete();

  if (loading) return <div>Loading todos...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <button onClick={() => createTodo({ text: 'New Todo', completed: false })}>
        Add Todo
      </button>
      {todos?.map(todo => (
        <div key={todo.id}>
          <span>{todo.text}</span>
          <button onClick={() => updateTodo(todo.id, { ...todo, completed: !todo.completed })}>
            Toggle
          </button>
          <button onClick={() => deleteTodo(todo.id)}>Delete</button>
        </div>
      ))}
    </div>
  );
}

🚀 Advanced Features

Nested Resources

Handle complex resource relationships with ease:

// Comments belong to todos
const commentApi = createDomainApi(
  'todos/:todoId/comments',
  commentSchema,
  commentUpsertSchema,
  {
    optimistic: false,
    queryParams: {
      static: { include: 'author' },
      dynamic: ['status', 'sortBy']
    }
  }
);

// Usage with builder pattern
function TodoComments({ todoId }: { todoId: string }) {
  const api = commentApi
    .withParams({ todoId })
    .withQuery({ status: 'published', sortBy: 'createdAt' });
    
  const { data: comments } = api.useList();
  const { mutate: createComment } = api.useCreate();
  
  return (
    <div>
      {comments?.map(comment => (
        <div key={comment.id}>{comment.text}</div>
      ))}
      <button onClick={() => createComment({ text: 'New comment', authorId: 'user-1' })}>
        Add Comment
      </button>
    </div>
  );
}

Different Schemas for Operations

Use different schemas for entity responses vs create/update operations:

// Full entity schema (includes server-generated fields)
const userEntitySchema = z.object({
  name: z.string(),
  email: z.string().email(),
  avatar: z.string().url(), // Server-generated
  lastLoginAt: z.string().datetime(), // Server-managed
});

// Schema for create/update operations (excludes server-generated fields)
const userUpsertSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

const userApi = createDomainApi('users', userEntitySchema, userUpsertSchema);

Type Extraction

Extract TypeScript types from your APIs:

import { ExtractEntityType, ExtractInputType } from '@skylabs-digital/react-proto-kit';

type User = ExtractEntityType<typeof userApi>;
// Result: { id: string; createdAt: string; updatedAt: string; name: string; email: string; avatar: string; lastLoginAt: string; }

type UserInput = ExtractInputType<typeof userApi>;
// Result: { name: string; email: string; }

Form Integration

Built-in form handling with validation:

import { useFormData } from '@skylabs-digital/react-proto-kit';

function UserForm() {
  const { mutate: createUser } = userApi.useCreate();
  const { values, errors, handleInputChange, handleSubmit, reset } = useFormData(
    userUpsertSchema,
    { name: '', email: '' }
  );

  const onSubmit = handleSubmit(async (data) => {
    await createUser(data);
    reset();
  });

  return (
    <form onSubmit={onSubmit}>
      <input
        name="name"
        value={values.name || ''}
        onChange={handleInputChange}
        placeholder="Name"
      />
      {errors.name && <span>{errors.name}</span>}
      
      <input
        name="email"
        value={values.email || ''}
        onChange={handleInputChange}
        placeholder="Email"
      />
      {errors.email && <span>{errors.email}</span>}
      
      <button type="submit">Create User</button>
    </form>
  );
}

URL State Management

Synchronize component state with URL parameters:

import { useUrlSelector } from '@skylabs-digital/react-proto-kit';

function TodoList() {
  const [filter, setFilter] = useUrlSelector('filter', (value: string) => value as FilterType);
  const [page, setPage] = useUrlSelector('page', (value: string) => parseInt(value) || 1);
  
  const { data: todos } = todoApi.useList({
    page,
    limit: 10,
    filter: filter || 'all'
  });

  return (
    <div>
      <button onClick={() => setFilter('active')}>Show Active</button>
      <button onClick={() => setFilter('completed')}>Show Completed</button>
      <button onClick={() => setPage(page + 1)}>Next Page</button>
      {/* Render todos */}
    </div>
  );
}

Partial Updates with PATCH

Use PATCH for efficient partial updates:

function TodoItem({ todo }: { todo: Todo }) {
  const { mutate: patchTodo } = todoApi.usePatch();
  
  // Only update the completed field
  const toggleCompleted = () => {
    patchTodo(todo.id, { completed: !todo.completed });
  };
  
  return (
    <div>
      <span>{todo.text}</span>
      <button onClick={toggleCompleted}>
        {todo.completed ? 'Mark Incomplete' : 'Mark Complete'}
      </button>
    </div>
  );
}

Single Record APIs ⭐ NEW

For endpoints that return a single record instead of a list (settings, config, stats):

import { createSingleRecordApi, createSingleRecordReadOnlyApi } from '@skylabs-digital/react-proto-kit';

// Full CRUD for single record (settings, profile, etc.)
const settingsApi = createSingleRecordApi(
  'users/:userId/settings',
  settingsSchema,
  settingsInputSchema,
  { 
    allowReset: true,           // Enable useReset() for reset to defaults
    refetchInterval: 30000      // Auto-refresh every 30 seconds
  }
);

// Read-only for computed/aggregate data (stats, analytics)
const statsApi = createSingleRecordReadOnlyApi(
  'dashboard/stats',
  statsSchema,
  { refetchInterval: 60000 }
);

Usage in components:

function UserSettings({ userId }: { userId: string }) {
  const api = settingsApi.withParams({ userId });
  
  // Fetch single record (not a list)
  const { data: settings, loading, refetch } = api.useRecord();
  
  // Update entire record (PUT - no ID needed)
  const { mutate: updateSettings, loading: updating } = api.useUpdate();
  
  // Partial update (PATCH - no ID needed)
  const { mutate: patchSettings } = api.usePatch();
  
  // Reset to defaults (DELETE - optional, requires allowReset: true)
  const { mutate: resetSettings } = api.useReset();
  
  const handleSave = async (newSettings: SettingsInput) => {
    await updateSettings(newSettings);
  };
  
  const toggleDarkMode = async () => {
    await patchSettings({ darkMode: !settings?.darkMode });
  };

  if (loading) return <Spinner />;
  
  return (
    <SettingsForm 
      settings={settings} 
      onSave={handleSave}
      onToggleDarkMode={toggleDarkMode}
      onReset={resetSettings}
    />
  );
}

// Read-only dashboard stats
function DashboardStats() {
  const { data: stats, loading } = statsApi.useRecord();
  
  if (loading) return <StatsSkeleton />;
  
  return (
    <div>
      <StatCard title="Total Users" value={stats?.totalUsers} />
      <StatCard title="Active Today" value={stats?.activeToday} />
    </div>
  );
}

Key differences from createDomainApi:

| Feature | createDomainApi | createSingleRecordApi | createSingleRecordReadOnlyApi | |---------|-------------------|-------------------------|--------------------------------| | useList | ✅ | ❌ | ❌ | | useById | ✅ | ❌ | ❌ | | useRecord | ❌ | ✅ | ✅ | | useCreate | ✅ | ❌ | ❌ | | useUpdate | ✅ (with ID) | ✅ (no ID) | ❌ | | usePatch | ✅ (with ID) | ✅ (no ID) | ❌ | | useDelete | ✅ | ❌ | ❌ | | useReset | ❌ | ✅ (optional) | ❌ | | refetchInterval | ❌ | ✅ | ✅ |

Data Orchestrator

Manage multiple API calls in a single component with smart loading states. Choose between Hook (flexible) or HOC (declarative) patterns:

Hook Pattern

import { useDataOrchestrator } from '@skylabs-digital/react-proto-kit';

function Dashboard() {
  const { data, isLoading, isFetching, hasErrors, errors, retryAll } = useDataOrchestrator({
    required: {
      users: userApi.useList,
      products: productApi.useList,
    },
    optional: {
      stats: statsApi.useQuery,
    },
  });

  if (isLoading) return <FullPageLoader />;
  if (hasErrors) return <ErrorPage errors={errors} onRetry={retryAll} />;

  return (
    <div>
      {isFetching && <TopBarSpinner />}
      <h1>Users: {data.users!.length}</h1>
      <h1>Products: {data.products!.length}</h1>
    </div>
  );
}

HOC Pattern (with Refetch)

import { withDataOrchestrator } from '@skylabs-digital/react-proto-kit';

interface DashboardData {
  users: User[];
  products: Product[];
}

function DashboardContent({ users, products, orchestrator }: DashboardData & { orchestrator: any }) {
  return (
    <div>
      {/* Refresh all data */}
      <button onClick={orchestrator.retryAll} disabled={orchestrator.isFetching}>
        {orchestrator.isFetching ? 'Refreshing...' : 'Refresh All'}
      </button>
      
      <h1>Users: {users.length}</h1>
      
      {/* Refresh individual resource */}
      <button onClick={() => orchestrator.retry('products')}>Refresh Products</button>
      {orchestrator.loading.products && <Spinner />}
      
      <h1>Products: {products.length}</h1>
    </div>
  );
}

export const Dashboard = withDataOrchestrator<DashboardData>(DashboardContent, {
  hooks: {
    users: userApi.useList,
    products: productApi.useList,
  }
});

URL-Driven Data with Auto-Refetch ⭐ NEW

Perfect for tabs, filters, and pagination driven by URL parameters:

import { withDataOrchestrator, useUrlTabs, useUrlParam } from '@skylabs-digital/react-proto-kit';

interface TodoListData {
  todos: Todo[];
}

function TodoListContent({ todos, orchestrator }: TodoListData & { orchestrator: any }) {
  const [activeTab, setTab] = useUrlTabs('status', ['active', 'completed', 'archived'], 'active');

  return (
    <div>
      {/* Tab navigation updates URL */}
      <button onClick={() => setTab('active')}>Active</button>
      <button onClick={() => setTab('completed')}>Completed</button>
      <button onClick={() => setTab('archived')}>Archived</button>

      {/* Non-blocking refetch indicator */}
      {orchestrator.isFetching && <span>🔄 Refreshing...</span>}

      {/* List updates automatically when tab changes */}
      <ul>
        {todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
      </ul>
    </div>
  );
}

const TodoListWithData = withDataOrchestrator<TodoListData>(TodoListContent, {
  hooks: {
    todos: () => {
      const [status] = useUrlParam('status'); // Reads ?status=active
      return todoApi.withQuery({ status: status || 'active' }).useList();
    },
  },
  options: {
    watchSearchParams: ['status'], // Auto-refetch when ?status= changes
    refetchBehavior: 'stale-while-revalidate', // Smooth transitions (default)
  },
});

How it works:

  1. User clicks "Completed" tab → URL updates to ?status=completed
  2. watchSearchParams detects change
  3. Hook re-executes with new status value
  4. stale-while-revalidate shows "Active" todos while loading "Completed"
  5. Smooth transition when new data arrives

Key Features:

  • isLoading: Blocks rendering during first load of required resources
  • isFetching: Shows non-blocking indicator for refetches
  • watchSearchParams: Auto-refetch when specified URL params change ⭐ NEW
  • refetchBehavior: 'stale-while-revalidate' (smooth) or 'blocking' (explicit) ⭐ NEW
  • Required vs Optional: Control which resources block rendering
  • Granular Retry: Retry individual resources or all at once
  • Orchestrator Prop: HOC injects refetch capabilities automatically
  • Type-Safe: Full TypeScript inference for all data

Refetch Behaviors:

  • stale-while-revalidate (default): Shows previous data while fetching new data. Perfect for tabs, filters, pagination.
  • blocking: Clears data and shows loading state. Use for critical updates where stale data is misleading.

See Data Orchestrator Documentation for complete examples.

Local Storage Mode

Perfect for prototyping without a backend:

function App() {
  return (
    <BrowserRouter>
      <ApiClientProvider connectorType="localStorage">
        <GlobalStateProvider>
          {/* Your app works exactly the same! */}
        </GlobalStateProvider>
      </ApiClientProvider>
    </BrowserRouter>
  );
}

📚 API Reference

createDomainApi(path, entitySchema, upsertSchema, config?)

Creates a complete CRUD API for a resource.

Parameters:

  • path: string - Resource path (e.g., 'users', 'todos/:todoId/comments')
  • entitySchema: ZodSchema - Schema for entity responses
  • upsertSchema: ZodSchema - Schema for create/update operations
  • config?: object - Optional configuration

Config Options:

{
  optimistic?: boolean;        // Enable optimistic updates (default: true)
  cacheTime?: number;         // Cache duration in milliseconds
  queryParams?: {
    static?: Record<string, any>;   // Always included parameters
    dynamic?: string[];             // Runtime configurable parameters
  };
}

Returns: API object with methods:

  • useList(params?) - Fetch list of entities
  • useQuery(id) / useById(id) - Fetch single entity
  • useCreate() - Create new entity
  • useUpdate() - Update entire entity (PUT)
  • usePatch() - Partial update (PATCH)
  • useDelete() - Delete entity
  • withParams(params) - Inject path parameters (builder pattern)
  • withQuery(params) - Inject query parameters (builder pattern)

createSingleRecordApi(path, entitySchema, upsertSchema, config?)

Creates an API for single-record endpoints (settings, config, profile).

Parameters:

  • path: string - Resource path (e.g., 'settings', 'users/:userId/profile')
  • entitySchema: ZodSchema - Schema for entity responses
  • upsertSchema: ZodSchema - Schema for update operations
  • config?: object - Optional configuration

Config Options:

{
  cacheTime?: number;           // Cache duration in milliseconds
  refetchInterval?: number;     // Auto-refetch interval (for real-time data)
  allowReset?: boolean;         // Enable useReset() method
  queryParams?: {
    static?: Record<string, any>;
    dynamic?: string[];
  };
}

Returns: API object with methods:

  • useRecord() - Fetch single record
  • useUpdate() - Update entire record (PUT)
  • usePatch() - Partial update (PATCH)
  • useReset() - Reset to defaults (DELETE) - only if allowReset: true
  • withParams(params) - Inject path parameters
  • withQuery(params) - Inject query parameters

createSingleRecordReadOnlyApi(path, entitySchema, config?)

Creates a read-only API for computed/aggregate endpoints (stats, analytics).

Parameters:

  • path: string - Resource path (e.g., 'dashboard/stats')
  • entitySchema: ZodSchema - Schema for entity responses
  • config?: object - Optional configuration (same as above, minus allowReset)

Returns: API object with methods:

  • useRecord() - Fetch single record
  • withParams(params) - Inject path parameters
  • withQuery(params) - Inject query parameters

Hooks

All hooks return objects with consistent interfaces:

Query Hooks (useList, useQuery, useById):

{
  data: T | T[] | null;
  loading: boolean;
  error: Error | null;
  refetch: () => Promise<void>;
}

Mutation Hooks (useCreate, useUpdate, usePatch, useDelete):

{
  mutate: (data: T, id?: string) => Promise<T>;
  loading: boolean;
  error: Error | null;
}

Type Utilities

  • ExtractEntityType<T> - Extract complete entity type with auto-generated fields
  • ExtractInputType<T> - Extract input type for create/update operations

📁 Examples

The repository includes comprehensive examples:

Run examples locally:

git clone https://github.com/skylabs-digital/react-proto-kit.git
cd react-proto-kit/examples/todo-with-backend
npm install
npm run dev

🎨 UI Components

Snackbar Notifications

Built-in toast-style notifications with auto-dismiss and queue management:

import { SnackbarProvider, SnackbarContainer, useSnackbar } from '@skylabs-digital/react-proto-kit';

// Setup (once in your app)
function App() {
  return (
    <SnackbarProvider>
      <SnackbarContainer position="top-right" maxVisible={3} />
      <YourApp />
    </SnackbarProvider>
  );
}

// Use in any component
function SaveButton() {
  const { showSnackbar } = useSnackbar();
  
  const handleSave = async () => {
    try {
      await saveData();
      showSnackbar({
        message: 'Changes saved successfully!',
        variant: 'success',
        duration: 3000
      });
    } catch (error) {
      showSnackbar({
        message: 'Error saving changes',
        variant: 'error',
        duration: 5000
      });
    }
  };
  
  return <button onClick={handleSave}>Save</button>;
}

Snackbar Features:

  • ✅ 4 variants: success, error, warning, info
  • ✅ Auto-dismiss with configurable timeout
  • ✅ Queue system for multiple notifications
  • ✅ Optional action buttons (undo, etc.)
  • ✅ Fully customizable via SnackbarComponent prop
  • ✅ 6 position options (top/bottom, left/center/right)
  • ✅ Portal rendering for proper z-index

Custom Snackbar Component:

import { SnackbarItemProps } from '@skylabs-digital/react-proto-kit';

function CustomSnackbar({ snackbar, onClose, animate }: SnackbarItemProps) {
  return (
    <div style={{ /* your custom styles */ }}>
      <span>{snackbar.message}</span>
      {snackbar.action && (
        <button onClick={() => {
          snackbar.action.onClick();
          onClose(snackbar.id);
        }}>
          {snackbar.action.label}
        </button>
      )}
      <button onClick={() => onClose(snackbar.id)}>×</button>
    </div>
  );
}

// Use custom component
<SnackbarContainer SnackbarComponent={CustomSnackbar} />

Integration with CRUD APIs:

const todosApi = createDomainApi('todos', todoSchema);
const { showSnackbar } = useSnackbar();

const createMutation = todosApi.useCreate({
  onSuccess: () => showSnackbar({ message: 'Todo created!', variant: 'success' }),
  onError: (e) => showSnackbar({ message: e.message, variant: 'error' })
});

📖 Documentation

Comprehensive documentation is available in the docs/ directory:

🛠 Backend Integration

Express.js Example

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());
app.use(express.json());

let todos = [];
let nextId = 1;

// GET /todos
app.get('/todos', (req, res) => {
  res.json(todos);
});

// POST /todos
app.post('/todos', (req, res) => {
  const todo = {
    id: String(nextId++),
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    ...req.body
  };
  todos.push(todo);
  res.status(201).json(todo);
});

// PUT /todos/:id
app.put('/todos/:id', (req, res) => {
  const index = todos.findIndex(t => t.id === req.params.id);
  if (index === -1) return res.status(404).json({ error: 'Todo not found' });
  
  todos[index] = {
    ...todos[index],
    ...req.body,
    updatedAt: new Date().toISOString()
  };
  res.json(todos[index]);
});

// PATCH /todos/:id
app.patch('/todos/:id', (req, res) => {
  const index = todos.findIndex(t => t.id === req.params.id);
  if (index === -1) return res.status(404).json({ error: 'Todo not found' });
  
  todos[index] = {
    ...todos[index],
    ...req.body,
    updatedAt: new Date().toISOString()
  };
  res.json(todos[index]);
});

// DELETE /todos/:id
app.delete('/todos/:id', (req, res) => {
  const index = todos.findIndex(t => t.id === req.params.id);
  if (index === -1) return res.status(404).json({ error: 'Todo not found' });
  
  todos.splice(index, 1);
  res.status(204).send();
});

app.listen(3001, () => {
  console.log('Server running on http://localhost:3001');
});

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

git clone https://github.com/skylabs-digital/react-proto-kit.git
cd react-proto-kit
npm install
npm run dev

Running Tests

npm test              # Run tests once
npm run test:watch    # Run tests in watch mode
npm run test:coverage # Run tests with coverage

📄 License

MIT © Skylabs Digital

🙏 Acknowledgments

Built with ❤️ by the Skylabs Digital team. Special thanks to:

  • Zod for amazing schema validation
  • React for the incredible ecosystem
  • The open-source community for inspiration and feedback

Ready to prototype at lightning speed?Get started now or explore the examples!