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

kittle-ui

v0.2.0

Published

Reusable React UI for the application security and execution specification: data tables, schema forms, query builders, navigation, feedback, and UI primitives.

Downloads

485

Readme

kittle-ui

Reusable React UI for the application security and execution specification: data tables, schema-driven forms, query builders, navigation, feedback states, and UI primitives.

The package is application-agnostic. Every platform capability it needs — navigation, permissions, date formatting, analytics, notifications, and request execution — is supplied by the host application through the UiRuntime ports, so the same components work with any router, permission model, or HTTP client.

What this package provides

  • DataTable — server/client pagination, search, filters, grouped columns, column visibility and reordering, selection, row operations, export hooks, and granular permission checks.
  • SchemaForm — Zod-driven form generation with nested arrays, conditional visibility, validation modes, server errors, submission guards, field slots, and custom field renderers.
  • QueryBuilder — simple and advanced modes, nested groups, operator metadata, persisted state, and custom value renderers.
  • Sidebar — recursive permission filtering, search, active descendant state, collapsible navigation with flyouts, persistence, and keyboard activation.
  • UI primitives — buttons, dialogs, sheets, inputs, selects, comboboxes, autocomplete, multi-select, cards, tabs, tables, feedback states, and more, styled with Tailwind CSS and Base UI.

Requirements

  • Node.js 20 or newer (the package is ESM-only)
  • React 19
  • Tailwind CSS in the host application, with the installed package output included in your content sources

Installation

npm install kittle-ui

Peer dependencies (npm 7+ installs them automatically): react, react-dom, @tanstack/react-table, @tanstack/react-form, @tanstack/react-query, zod, lucide-react, react-i18next, and sonner.

Entry points

The root barrel exposes the runtime ports and the shared form, query, menu, and table contracts; each area also has a directory entrypoint.

| Import specifier | Contents | | ------------------------------- | ------------------------------------------------------------------------------ | | kittle-ui | Runtime ports, provider, and the core form/query/menu/table contracts | | kittle-ui/core/runtime | UiRuntimeProvider, useUiRuntime, RuntimeLink, ability and entity helpers | | kittle-ui/core/form | Schema parsing, field registry, validation and condition logic, layout | | kittle-ui/core/query | Query-builder state, operators, storage, Zod filter fields | | kittle-ui/core/table | Table state, operation validation, permissions, column reorder | | kittle-ui/core/menu | Menu filtering, hotkeys, shared constants | | kittle-ui/react/data-table | DataTable plus its parts and hooks | | kittle-ui/react/form | SchemaForm, FieldRenderer, FieldArray, and specialized field pickers | | kittle-ui/react/query-builder | QueryBuilder and its parts | | kittle-ui/react/sidebar | Sidebar, secondary navigation, flyouts, and the user menu | | kittle-ui/react/ui/* | UI primitives | | kittle-ui/react/feedback/* | Empty and inline-error states |

Every built module is also reachable through the wildcard deep paths (kittle-ui/core/* and kittle-ui/react/*); the table above lists the stable entrypoints.

Binding the runtime

Mount UiRuntimeProvider once and supply the ports the UI consumes. Each port is optional and falls back to safe defaults, but unbound capabilities fail loudly at the point of use instead of silently returning incorrect data.

import { UiRuntimeProvider } from "kittle-ui/core/runtime"

export function App() {
  return (
    <UiRuntimeProvider
      runtime={{
        // Router integration; `RuntimeLink` and the Sidebar use these.
        navigation: { pathname, search, navigate },
        // CASL-like ability: { can(action, subject) }.
        ability,
        // Hook-shaped adapters so call order matches TanStack Query.
        request: { useMutation, useQuery },
        notify: { success, error },
      }}
    >
      {/* routes */}
    </UiRuntimeProvider>
  )
}

Styling

Components use Tailwind CSS utility classes against host theme tokens (for example bg-card, text-muted-foreground, and bg-primary). Ensure your Tailwind content sources include the installed package, e.g. Tailwind v4:

@source "../node_modules/kittle-ui/dist";

Styling internal parts

Use className to style a component's outer element and classNames to override named internal parts. No styling provider or wrapper component is required:

import { QueryBuilder } from "kittle-ui/react/query-builder"

function Filters() {
  return (
    <QueryBuilder
      schema={filterSchema}
      className="w-full"
      classNames={{
        container: "rounded-none border bg-background p-6 sm:p-6 shadow-none",
        group: "rounded-md bg-muted",
        groupHeader: "p-4",
        condition: "py-3",
        actions: "border-0",
      }}
    />
  )
}

Slot names are component-specific and typed for autocomplete. Unspecified slots retain their existing styles. Overrides are merged after the default and state classes with tailwind-merge, so conflicting Tailwind utilities can replace defaults. Existing className props retain their original behavior. Styling does not replace component behavior or permission checks. Responsive and state variants remain independent: use sm:p-6 to override sm:p-4, or dark:bg-background to override a dark-mode background.

| Component | Slot type | Type import | | ------------ | ------------------------ | ---------------------- | | QueryBuilder | QueryBuilderClassNames | kittle-ui/core/query | | DataTable | DataTableClassNames | kittle-ui/core/table | | SchemaForm | SchemaFormClassNames | kittle-ui/core/form | | Sidebar | SidebarClassNames | kittle-ui/core/menu |

These types are also available from kittle-ui. Composite primitives and feedback components export their styling types from their individual modules.

Repeated parts share their slot styles: for example, QueryBuilder's group and condition overrides also apply inside nested groups. Simple primitives such as Button and the individual Card parts already accept className; use that prop when rendering them directly.

You can also reuse a plain typed object across instances without a provider or wrapper:

import type { QueryBuilderClassNames } from "kittle-ui/core/query"

const filterStyles = {
  container: "rounded-none border shadow-none",
  groupHeader: "p-4",
} satisfies QueryBuilderClassNames

<QueryBuilder schema={filterSchema} classNames={filterStyles} />

For filters embedded in DataTable, pass QueryBuilder styles through filterConfig.queryBuilderClassNames alongside the filter schema:

const filterConfig = {
  schema: filterSchema,
  queryBuilderClassNames: filterStyles,
}

Nested components and field-specific styling

partClassNames styles the components created internally by a composite. For example, QueryBuilder can forward styles into the MultiSelect popup and chips rather than only its trigger:

function Filters() {
  return (
    <QueryBuilder
      schema={filterSchema}
      partClassNames={{
        valueSelect: { content: "rounded-none", item: "py-3" },
        multiSelect: {
          popover: "rounded-none shadow-none",
          chip: "bg-secondary text-secondary-foreground",
        },
      }}
      fieldStyles={{
        status: {
          classNames: { valueWrapper: "min-w-64" },
          partClassNames: { multiSelect: { chip: "font-semibold" } },
        },
      }}
    />
  )
}

QueryBuilder field keys are filter field IDs and apply in both simple and advanced modes, including conditions inside nested groups. SchemaForm uses schema paths, with * matching an array index:

function Editor() {
  return (
    <SchemaForm
      schema={formSchema}
      classNames={{ control: "text-sm" }}
      partClassNames={{ multiSelect: { popover: "rounded-none" } }}
      fieldStyles={{
        email: { classNames: { control: "font-mono" } },
        "rows.*.quantity": { classNames: { control: "text-right" } },
        "orders.*.lines.*.sku": { classNames: { control: "uppercase" } },
      }}
    />
  )
}

Shared styles are merged with field-specific styles rather than replaced wholesale. Field-specific utilities win conflicts; in forms, more specific path matches override less specific wildcard matches, and exact paths win over wildcard paths. Paths describe schema fields, not generated DOM IDs. Prefer wildcard paths for array rows so styles continue to apply as items are added or removed. Existing array listClassName overrides remain last.

DataTable also exposes partClassNames for its feedback and operation Dialog/Sheet components. Its filter configuration forwards queryBuilderPartClassNames and queryBuilderFieldStyles in addition to queryBuilderClassNames.

Public nested styling contracts are QueryBuilderPartClassNames, QueryBuilderFieldStyles, SchemaFormPartClassNames, SchemaFormFieldStyles, and DataTablePartClassNames, exported through their respective core entrypoints and the package root. Primitive slot types are exported from their owning modules. Autocomplete's root configuration styles its generated parts; directly supplied part className overrides remain available when composing it yourself.

Use structural slots for the parent-owned layout and nested-part slots for the child component's own elements:

  • QueryBuilder: groups/conditions/actions versus selector popups and MultiSelect chips, options, and search controls.
  • SchemaForm: sections/fields/array actions versus picker, help, and selection-control internals.
  • DataTable: toolbar menus/pagination/state layouts versus feedback content and generated operation Dialog/Sheet surfaces.
  • Sidebar: header/search/menu details, secondary/flyout actions, user-menu sections/options, and the organization placeholder's visible parts.

Slot names are semantic rather than DOM selectors. A slot for repeated items styles all matching items, while a conditionally rendered slot only has an effect when its corresponding element is present.

Expanded styling surfaces

| Area | Representative structural slots | Nested part keys | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | | QueryBuilder | filterTrigger, groupLogicToggle, addConditionButton, removeConditionButton, rangeSeparator | fieldSelect, operatorSelect, valueSelect, multiSelect, emptyState | | SchemaForm | field, label, errorRow, arrayHeader, arrayAddButton, arrayRemoveButton, arrayEmpty, arrayCompactContent | select, multiSelect, autocomplete, password, help, datePicker, dateTimePicker, colorPicker | | DataTable | columnMenuContent, exportMenuItem, paginationSelect, paginationButton, loadingCell, errorRetry, operationConfirm | emptyState, errorMessage, operationDialog, operationSheet | | Sidebar | searchInput, headerTitle, menuBadge, menuHotkey, menuDisclosureIcon, secondaryBackButton, userMenuLanguageOption, organizationSwitcher | Sidebar parts share the SidebarClassNames map |

The exported slot types are the full supported inventory. For example, partClassNames.datePicker.day styles a form's calendar day buttons; partClassNames.dateTimePicker.timeInput styles its time controls; and partClassNames.multiSelect.chipRemove styles generated chip removal controls. Select part maps expose trigger, content, and item, including portaled content. Autocomplete's explicitly supplied part className takes precedence over its root's shared slot configuration.

Styles target existing elements, including portaled popups. They do not change selection, permissions, validation, disabled behavior, or navigation. Registered custom form controls and caller-rendered actions, field slots, and operation content remain caller-owned; their internal styles are not rewritten by these APIs. The OrganizationSwitcher is still a placeholder: its styling slots do not add organization-switching behavior.

Styling props do not introduce an additional client-side provider. Interactive components still require their existing React hooks and application setup.

License

MIT