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

dynamic-ui-kit

v0.5.0

Published

A generic, config-driven UI toolkit for dynamic tables and forms in React

Readme

dynamic-ui-kit

A framework-agnostic, JSON-driven UI component library for building dynamic tables, forms, and modals with React.

Features

  • 📋 JSON-driven configuration - Define tables and forms with simple JSON
  • 🔌 Adapter pattern - Plug in any backend/API with service adapters
  • 🧩 Extensible field system - Register custom field components
  • Built-in validation - Comprehensive validation with conditional logic
  • 🎣 Powerful hooks - Reusable state management for pagination, forms, selection
  • 🔧 Config validation - Validate JSON configs at development time
  • 📦 Tree-shakeable - Import only what you need

Installation

npm install dynamic-ui-kit
# or
yarn add dynamic-ui-kit

Quick Start

Using Hooks

import { usePaginatedData, useFormState } from 'dynamic-ui-kit';

function MyTable() {
  const { state, actions, getPageData } = usePaginatedData({
    initialData: myData,
    pageSize: 10,
  });

  return (
    <table>
      <tbody>
        {getPageData().map(row => (
          <tr key={row.id}>...</tr>
        ))}
      </tbody>
    </table>
  );
}

function MyForm() {
  const { values, errors, getFieldProps, handleSubmit } = useFormState({
    initialValues: { name: '', email: '' },
    sections: formConfig.sections,
    onSubmit: (data) => saveData(data),
  });

  return (
    <form onSubmit={handleSubmit}>
      <input {...getFieldProps('name')} />
      <input {...getFieldProps('email')} />
      <button type="submit">Save</button>
    </form>
  );
}

Using Service Adapters

import { createServiceAdapter } from 'dynamic-ui-kit';

const productsService = createServiceAdapter({
  baseUrl: '/api',
  entityName: 'products',
  headers: () => ({
    'Authorization': `Bearer ${getToken()}`,
  }),
});

// Fetch paginated data
const { data, total, page } = await productsService.list({
  page: 1,
  pageSize: 20,
  search: 'widget',
  sortColumn: 'name',
  sortDirection: 'asc',
});

// CRUD operations
await productsService.create({ name: 'New Product' });
await productsService.update(1, { name: 'Updated Product' });
await productsService.delete(1);

Using the Field Registry

import { 
  FieldRegistry, 
  registerField, 
  useFieldFactory 
} from 'dynamic-ui-kit';

// Register a custom field
registerField({
  type: 'color-picker',
  component: ColorPickerField,
  displayName: 'Color Picker',
});

// Use in a component
function DynamicForm({ config }) {
  const { renderSection } = useFieldFactory();
  const { values, handleChange, errors, touched } = useFormState({...});

  return (
    <form>
      {config.sections.map(section =>
        renderSection(section, values, {
          onChange: handleChange,
          errors,
          touched,
        })
      )}
    </form>
  );
}

Validating Configuration

import { validateConfig, parseConfig } from 'dynamic-ui-kit';

// Validate a config
const result = validateConfig(rawConfig);
if (!result.valid) {
  console.error('Config errors:', result.errors);
}

// Parse and normalize config
const tableConfig = parseConfig(rawTableJson, {
  generateLabels: true,
  defaultSortable: true,
});

Module Exports

The library supports tree-shaking with multiple entry points:

// Main entry (all exports)
import { usePaginatedData, validateConfig } from 'dynamic-ui-kit';

// Core utilities
import { formatValue, cn, debounce } from 'dynamic-ui-kit/core';

// Hooks only
import { useFormState, useTableSelection } from 'dynamic-ui-kit/hooks';

// Adapters
import { createServiceAdapter, createAxiosAdapter } from 'dynamic-ui-kit/adapters';

// Field system
import { FieldRegistry, TextField, baseFields } from 'dynamic-ui-kit/fields';

// Config system
import { validateFormConfig, parseTableConfig } from 'dynamic-ui-kit/config';

API Reference

Hooks

| Hook | Description | |------|-------------| | usePaginatedData | Pagination, sorting, search state management | | useFormState | Form values, validation, touched state | | useColumnConfig | Column visibility, ordering, resizing | | useTableSelection | Single/multiple row selection | | useFieldFactory | Render fields from JSON config | | useFieldRegistry | Access the field component registry |

Adapters

| Adapter | Description | |---------|-------------| | createServiceAdapter | Fetch-based API adapter | | createAxiosAdapter | Axios-based API adapter | | createFormAdapter | Form data transformation adapter |

Field Components

| Component | Type | |-----------|------| | TextField | text, email, password, tel, url | | NumberField | number, percent | | CurrencyField | currency | | TextareaField | textarea | | SelectField | select | | CheckboxField | checkbox | | SwitchField | switch | | HiddenField | hidden |

Config Validators

| Function | Description | |----------|-------------| | validateTableConfig | Validate table JSON config | | validateFormConfig | Validate form JSON config | | validateConfig | Auto-detect and validate config | | parseTableConfig | Parse and normalize table config | | parseFormConfig | Parse and normalize form config |

Components

| Component | Description | |-----------|-------------| | TableRenderer | Configurable data table with pagination, sorting, filtering, and selection | | FormRenderer | Dynamic form renderer with sections, validation, and conditional fields | | DynamicModal | Modal wrapper with tabs, forms, and action buttons |

Theming

The library supports light/dark theming:

  • By default components follow the user's operating system preference via prefers-color-scheme.
  • Components accept a theme prop with values 'system', 'light', or 'dark' to force a specific theme per component.

Using TableRenderer

import { TableRenderer } from 'dynamic-ui-kit';
import 'dynamic-ui-kit/styles';

const tableConfig = {
  columns: [
    { key: 'name', header: 'Name', sortable: true },
    { key: 'email', header: 'Email' },
    { key: 'status', header: 'Status', type: 'badge' },
  ],
  pagination: { enabled: true, pageSize: 10 },
};

function UserTable() {
  const [users, setUsers] = useState([]);
  const [page, setPage] = useState(1);

  return (
    <TableRenderer
      config={tableConfig}
      data={users}
      totalCount={100}
      page={page}
      pageSize={10}
      onPageChange={setPage}
      onRowClick={(row) => console.log('Clicked:', row)}
      selectable
      selectionMode="multiple"
      onSelectionChange={(ids) => console.log('Selected:', ids)}
      striped
      hoverable
    />
  );
}

TableRenderer Props

| Prop | Type | Description | |------|------|-------------| | config | TableConfig | Table configuration object | | data | T[] | Data array to display | | totalCount | number | Total records for pagination | | page | number | Current page (1-indexed) | | pageSize | number | Items per page | | loading | boolean | Show loading state | | error | string | Error message to display | | onPageChange | (page: number) => void | Page change callback | | onSortChange | (sort) => void | Sort change callback | | onRowClick | (row, index) => void | Row click callback | | selectable | boolean | Enable row selection | | selectionMode | 'single' \| 'multiple' | Selection mode | | striped | boolean | Stripe alternate rows | | hoverable | boolean | Add hover effect | | bordered | boolean | Add cell borders | | compact | boolean | Compact padding | | stickyHeader | boolean | Sticky header on scroll | | theme | 'system' \| 'light' \| 'dark' | Theme mode for the table. Defaults to system (follows prefers-color-scheme). | | renderActions | (row, index) => ReactNode | Custom actions column |

Using FormRenderer

import { FormRenderer } from 'dynamic-ui-kit';
import 'dynamic-ui-kit/styles';

const formConfig = {
  id: 'product-form',
  title: 'Edit Product',
  sections: [
    {
      id: 'basic',
      title: 'Basic Information',
      columns: 2,
      fields: [
        { name: 'name', type: 'text', label: 'Product Name', required: true },
        { name: 'sku', type: 'text', label: 'SKU' },
        { name: 'description', type: 'textarea', label: 'Description', colSpan: 2 },
      ],
    },
    {
      id: 'pricing',
      title: 'Pricing',
      collapsible: true,
      fields: [
        { name: 'price', type: 'currency', label: 'Price', required: true },
        { name: 'cost', type: 'currency', label: 'Cost' },
        { 
          name: 'margin', 
          type: 'percent', 
          label: 'Margin',
          computed: { formula: '(price - cost) / price * 100', deps: ['price', 'cost'] }
        },
      ],
    },
  ],
};

function ProductForm() {
  const formRef = useRef<FormRendererRef>(null);
  const [values, setValues] = useState({ name: '', price: 0 });

  const handleSubmit = async (data) => {
    await api.post('/products', data);
  };

  return (
    <FormRenderer
      ref={formRef}
      config={formConfig}
      values={values}
      onChange={(name, value, allValues) => setValues(allValues)}
      onSubmit={handleSubmit}
      onCancel={() => history.back()}
    />
  );
}

FormRenderer Props

| Prop | Type | Description | |------|------|-------------| | config | FormConfig | Form configuration object | | values | T | Current form values (controlled) | | initialValues | T | Initial values (uncontrolled) | | errors | FormErrors | External validation errors | | onSubmit | (values: T) => void | Form submit handler | | onCancel | () => void | Cancel button handler | | onChange | (name, value, values) => void | Field change handler | | loading | boolean | Disable form while loading | | disabled | boolean | Disable all fields | | readOnly | boolean | Make all fields read-only | | renderField | (props) => ReactNode | Custom field renderer | | renderSection | (props) => ReactNode | Custom section renderer | | hideButtons | boolean | Hide submit/cancel buttons | | header | ReactNode | Custom header content | | footer | ReactNode | Custom footer content |

Using DynamicModal

import { DynamicModal } from 'dynamic-ui-kit';
import 'dynamic-ui-kit/styles';

function EditProductModal({ product, isOpen, onClose }) {
  return (
    <DynamicModal
      open={isOpen}
      onClose={onClose}
      title="Edit Product"
      subtitle={`Editing: ${product.name}`}
      size="lg"
      config={formConfig}
      initialValues={product}
      onSubmit={async (values) => {
        await api.put(`/products/${product.id}`, values);
        onClose();
      }}
      onDelete={async () => {
        await api.delete(`/products/${product.id}`);
        onClose();
      }}
      deleteConfirmation={{
        title: 'Delete Product',
        message: 'Are you sure? This action cannot be undone.',
        confirmLabel: 'Delete',
        confirmVariant: 'danger',
      }}
    />
  );
}

// With Tabs
function TabbedModal() {
  return (
    <DynamicModal
      open={true}
      onClose={() => {}}
      title="Product Details"
      tabs={[
        { id: 'info', label: 'Information', content: infoFormConfig },
        { id: 'pricing', label: 'Pricing', content: pricingFormConfig },
        { id: 'inventory', label: 'Inventory', content: inventoryFormConfig },
      ]}
      onSubmit={handleSave}
    />
  );
}

DynamicModal Props

| Prop | Type | Description | |------|------|-------------| | theme | 'system' \| 'light' \| 'dark' | (Optional) Force modal theme. Defaults to system which follows user's prefers-color-scheme. |

| Prop | Type | Description | |------|------|-------------| | open | boolean | Whether modal is visible | | onClose | () => void | Close handler | | title | string | Modal title | | subtitle | string | Modal subtitle | | size | 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full' | Modal size | | config | FormConfig | Form configuration | | tabs | ModalTab[] | Tab configurations | | initialValues | T | Initial form values | | onSubmit | (values: T) => void | Submit handler | | onDelete | () => void | Delete handler (shows delete button) | | actions | ModalAction[] | Custom action buttons | | loading | boolean | Loading state | | error | string | Error message | | closeOnBackdropClick | boolean | Close on overlay click | | closeOnEscape | boolean | Close on Escape key | | deleteConfirmation | ConfirmationConfig | Delete confirmation dialog |

Styling

Import the CSS styles to use the default styling:

// Import all styles
import 'dynamic-ui-kit/styles';

// Or import specific component styles
import 'dynamic-ui-kit/styles/table';
import 'dynamic-ui-kit/styles/form';
import 'dynamic-ui-kit/styles/modal';

The styles use CSS custom properties for easy theming:

:root {
  /* Table variables */
  --dui-table-primary: #3b82f6;
  --dui-table-bg: #ffffff;
  --dui-table-border-color: #e5e7eb;
  --dui-table-header-bg: #f9fafb;

  /* Form variables */
  --dui-form-primary: #3b82f6;
  --dui-form-bg: #ffffff;
  --dui-form-border-color: #d1d5db;
  --dui-form-text-error: #ef4444;

  /* Modal variables */
  --dui-modal-primary: #3b82f6;
  --dui-modal-bg: #ffffff;
  --dui-modal-overlay-bg: rgba(0, 0, 0, 0.5);
  /* ... more variables */
}

/* Dark mode is automatically supported via @media (prefers-color-scheme: dark) */

Internationalization (i18n)

The library includes built-in i18n support:

import { I18nProvider, initI18n } from 'dynamic-ui-kit';

// Initialize with Spanish
initI18n({ locale: 'es' });

// Or use the provider
function App() {
  return (
    <I18nProvider locale="es">
      <MyApp />
    </I18nProvider>
  );
}

Supported translations include form validation messages, table pagination, and more.

By default the library ships a canonical set of translations (defaultEnTranslations / defaultEsTranslations). If you want to override or extend translations from your application, pass a partial translations map to initI18n(...) — the library will deep-merge your translations with its defaults so missing keys fall back to the canonical set. You can also call getI18n().addTranslations(locale, {...}) at runtime to merge additional keys.

Configuration Examples

Table Configuration

{
  "columns": [
    { "key": "name", "header": "Product Name", "sortable": true },
    { "key": "price", "header": "Price", "type": "currency" },
    { "key": "stock", "header": "Stock", "type": "number" },
    { "key": "status", "header": "Status", "type": "badge" }
  ],
  "pagination": { "pageSize": 20 },
  "selectable": true
}

Form Configuration

{
  "entity": "product",
  "sections": [
    {
      "title": "Basic Info",
      "fields": [
        { "name": "name", "type": "text", "required": true },
        { "name": "description", "type": "textarea" },
        { 
          "name": "price", 
          "type": "currency", 
          "validation": [{ "min": 0, "message": "Price must be positive" }]
        }
      ]
    },
    {
      "title": "Inventory",
      "showWhen": { "field": "trackInventory", "operator": "eq", "value": true },
      "fields": [
        { "name": "stock", "type": "number", "min": 0 },
        { "name": "lowStockThreshold", "type": "number" }
      ]
    }
  ]
}

Development

# Install dependencies
npm install

# Build the library
npm run build

# Type check
npm run typecheck

# Run tests
npm test

# Development mode (watch)
npm run dev

License

MIT © Leandro Fusco

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting a PR.