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

@econneq/headless-crud

v3.0.2

Published

Headless, framework-agnostic CRUD engine — renderMode, tableVariant, embedded filter bar, REST+GraphQL, dark/light mode, full TypeScript.

Readme

@econneq/headless-crud v4

Headless, framework-agnostic CRUD engine for React, Next.js, React Native and any JS project. Zero external UI dependencies — batteries-included teal design system with full dark/light mode.


What's new in v4

| Feature | Details | |---|---| | renderMode | 'full' · 'table' · 'form' · 'action' | | tableVariant | 'enterprise' (A) · 'card' (B) | | Embedded filter bar | Accordion inside the table header — never detached | | FormOnly | Inline form without any button click | | ActionButton | Button-only render — reveals form/table on click | | REST adapter v2 | Entity-routing, body transforms, idPlacement | | Headless core | createCrudCore() — pure TS, no React, use anywhere | | Theme overrides | injectThemeOverrides() + buildThemeVars() for RN | | colorMode | 'light' · 'dark' · 'auto' (follows system) |


Install

npm install @econneq/headless-crud
# or
yarn add @econneq/headless-crud

Quick start

import { CrudEngine, CrudProvider } from '@econneq/headless-crud';

// Wrap your app once
<CrudProvider adapters={{ toast, useMutation }}>
  <App />
</CrudProvider>

// Use anywhere
<CrudEngine config={config} data={data} />

Render modes

Scenario 1 — Table only (no add button)

<CrudEngine
  config={config}
  data={data}
  renderMode="table"
  tableVariant="card"           // 'enterprise' or 'card'
  filter={{
    show: true,                 // attach filter bar to table header
    expanded: true,             // open by default
    mode: 'client',             // 'client' | 'server'
    fields: [
      { key: 'name',   label: 'Name',   type: 'text' },
      { key: 'status', label: 'Status', type: 'select', options: [...] },
    ],
    columns: { mobile: 1, tablet: 2, desktop: 3 },
  }}
/>

Scenario 2 — Form only (inline, no button required)

<CrudEngine
  config={config}
  data={[]}
  renderMode="form"
  formOnly={{
    autoOpen: true,             // default true — mounts immediately
    defaultMode: 'create',      // 'create' | 'edit'
    initialValues: { name: '' },
    showFooter: true,
    submitLabel: 'Save Patient',
  }}
  onMutationSuccess={() => router.push('/patients')}
/>

Scenario 3 — Action button only

<CrudEngine
  config={config}
  data={data}
  renderMode="action"
  actionButton={{
    label: '+ New / Search Patient',
    variant: 'primary',
    size: 'md',
    shows: 'form',              // 'form' | 'table' | 'both'
    position: 'slide-over',     // 'modal' | 'slide-over' | 'inline'
  }}
/>

Table variants

Variant A — Enterprise (default)

Teal header, export toolbar, column visibility panel.

<CrudEngine config={config} data={data} tableVariant="enterprise" />

Variant B — Card

PageHeader-style title, card container, inline search. Matches the patient table example exactly.

<CrudEngine
  config={config}
  data={data}
  tableVariant="card"
  cardTable={{
    title: "Today's Patients",
    subtitle: `${total} visits on ${TODAY}`,
    searchPlaceholder: 'Filter by name, phone…',
    showFooter: true,
    backLabel: '← Back',
    onBack: () => router.back(),
  }}
/>

Filter bar

The filter bar is embedded inside the table card — not a separate component.

<CrudEngine
  config={config}
  data={data}
  filter={{
    show: true,
    expanded: true,             // accordion starts open
    label: 'Search Filters',
    mode: 'server',
    apiType: 'rest',
    rest: {
      url: '/api/patients/search',
      method: 'GET',
      paramType: 'query',
    },
    behavior: { mode: 'debounce', debounceMs: 600 },
    useUrlParams: true,
    urlParamPrefix: 'f_',
    fields: [
      { key: 'name',       label: 'Name',     type: 'text' },
      { key: 'dob',        label: 'DOB',      type: 'date' },
      { key: 'sex',        label: 'Sex',      type: 'select', options: [
        { value: 'M', label: 'Male' },
        { value: 'F', label: 'Female' },
      ]},
      { key: 'phone',      label: 'Phone',    type: 'text' },
    ],
    columns: { mobile: 1, tablet: 2, desktop: 4 },
  }}
/>

Server filter — GraphQL

filter={{
  show: true,
  mode: 'server',
  apiType: 'graphql',
  onServerFilter: (filters) => {
    refetch({ where: buildWhereClause(filters) });
  },
  fields: [...],
}}

Dark / light mode

// Follow system preference (default)
<CrudEngine colorMode="auto" ... />

// Force dark
<CrudEngine colorMode="dark" ... />

// Force light
<CrudEngine colorMode="light" ... />

// Or via HTML attribute on your root element
document.documentElement.setAttribute('data-theme', 'dark');

Theme overrides

// Per-component override
<CrudEngine
  themeOverrides={{
    brand: '#7c3aed',
    font: 'Inter, sans-serif',
    tableHeaderBg: '#4c1d95',
  }}
  ...
/>

// Global override (call once at app startup)
import { injectThemeOverrides } from '@econneq/headless-crud';
injectThemeOverrides({ brand: '#7c3aed', radius: '8px' });

Framework agnostic — headless core

The createCrudCore() factory is pure TypeScript with zero React imports. Use it in Vue, Svelte, React Native, or vanilla JS:

import { createCrudCore } from '@econneq/headless-crud/core';

const engine = createCrudCore({ fields, defaults, validate });

// Subscribe to state changes
const unsubscribe = engine.subscribe((state) => {
  myStore.set(state);            // Svelte writable / Vue reactive
});

engine.openCreate();
engine.handleChange('name', 'Alice');
const errors = engine.doValidate(engine.state.formValues);
const payload = engine.buildPayload(engine.state.formValues);

React hook adapter (already built-in)

import { useCrudForm } from '@econneq/headless-crud';
const form = useCrudForm({ config, externalData });

React Native usage

Theme tokens as JS object (no CSS):

import { buildThemeVars } from '@econneq/headless-crud';
const theme = buildThemeVars(isDark);
// theme.brand, theme.surface, theme.tableHeaderBg, etc.

REST adapter

import { makeRestAdapter } from '@econneq/headless-crud/adapters/rest';

const adapters = makeRestAdapter({
  toast,
  baseUrl: '/api',
  entity: 'patients',
  getToken: () => localStorage.getItem('token'),
  idPlacement: 'path',          // PUT /api/patients/:id
  methods: { create: 'POST', update: 'PUT', delete: 'DELETE' },
  transformBody: (vars, action) => ({
    ...vars,
    _action: action,
  }),
});

GraphQL (Apollo) adapter

import { makeApolloAdapter } from '@econneq/headless-crud/adapters/apollo';
const adapters = makeApolloAdapter({ toast, useMutation, decodeId });

TanStack Query adapter

import { makeTanstackAdapter } from '@econneq/headless-crud/adapters/tanstack-query';
const { data, isLoading, isFetching, dataUpdatedAt } = useQuery(...);
<CrudEngine
  extraTableProps={{ isFetching, dataUpdatedAt }}
  onMutationSuccess={() => queryClient.invalidateQueries(...)}
  ...
/>

Full config reference

interface CrudEngineProps {
  config:            CrudConfig;
  data:              T[];
  loading?:          boolean;
  renderMode?:       'full' | 'table' | 'form' | 'action';   // default: 'full'
  tableVariant?:     'enterprise' | 'card';                   // default: 'enterprise'
  filter?:           FilterBarConfig;
  formOnly?:         FormOnlyConfig;
  actionButton?:     ActionButtonConfig;
  cardTable?:        CardTableConfig;
  colorMode?:        'light' | 'dark' | 'auto';              // default: 'auto'
  themeOverrides?:   ThemeOverrides;
  exportOptions?:    TableExportOptions;
  viewLink?:         ViewLinkConfig;
  tableControls?:    TableDisplayControls;
  fixedVariables?:   Record<string, any>;
  externalData?:     Record<string, any[]>;
  onRefresh?:        () => void;
  onMutationSuccess?: () => void;
  className?:        string;
  extraTableProps?:  Record<string, any>;
}

Changelog

v4.0.0

  • renderMode: table | form | action | full
  • tableVariant: enterprise (A) | card (B)
  • FilterBar embedded in table header with accordion, client + server + REST + GraphQL
  • FormOnly component for inline forms
  • ActionButton component for button-only render
  • createCrudCore() framework-agnostic headless engine
  • injectThemeOverrides() runtime token injection
  • buildThemeVars() for React Native / non-DOM
  • colorMode prop: light | dark | auto
  • REST adapter v2 with entity routing, idPlacement, body transforms
  • All filter fields responsive with configurable grid
  • URL param sync for filters