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

admin-ui-starter-kit

v0.9.1

Published

Opinionated React component kit for admin panels and SaaS dashboards, built on top of shadcn/ui + Tailwind CSS v4. Callback-driven, framework-agnostic, i18n-ready.

Readme

Admin UI Starter Kit

Opinionated React components for admin panels, SaaS interiors, and B2B back-offices. It sits above shadcn/ui and Tailwind CSS v4: shadcn provides primitives; this package provides the reusable admin layer those primitives usually become.

npm install admin-ui-starter-kit react react-dom tailwindcss lucide-react
import 'admin-ui-starter-kit/style.css';

import { Badge } from 'admin-ui-starter-kit/base/badge';
import { Button } from 'admin-ui-starter-kit/base/buttons';
import { Item, ItemActions, ItemContent, ItemMedia, ItemTitle } from 'admin-ui-starter-kit/base/item';
import { Mail } from 'lucide-react';

export function UserRow() {
  return (
    <Item>
      <ItemMedia variant="icon"><Mail /></ItemMedia>
      <ItemContent>
        <ItemTitle>[email protected]</ItemTitle>
      </ItemContent>
      <ItemActions>
        <Badge variant="success">Active</Badge>
        <Button size="small">Open</Button>
      </ItemActions>
    </Item>
  );
}

What You Get

  • Typography primitives: Text, Heading, DisplayLabel, Label, and TextLink with a dense admin scale.
  • Semantic display primitives: Value, Name, Initials, MonoValue, Link, Email, Phone, Url, Money, Number, Percent, FileSize, Duration, Dimensions, Date, Time, DateTime, DateRange, and RelativeTime for metadata, cards, tables, previews, and activity surfaces.
  • Base wrappers: buttons, badges, cards, item rows, form fields, comboboxes, tables, navigation menus, popovers, display states, toaster, maps, dates, and more.
  • Composed surfaces: admin settings rows, AI surfaces, metrics, commerce cards, data-display blocks, dark surfaces, navigation widgets, and timelines.
  • Feature shells: filters, data-view, comments, activities, async preview, media library, schema form, product editor, global search, overlays, Kanban, rich text, sync, and resource pages.
  • Layout chrome: page/header/sidebar/auth/settings/workspace shells that stay framework-agnostic.
  • Agentic docs: shipped Codex/Claude skills for maintainers and consumer migrations, plus generated component references from the showcase.
  • Canonical recipes: live operational index, resource detail, activity, and AI-assisted compositions under #/docs/composition-recipes.
  • Parity tooling: a scoped presentation lab under #/docs/parity-lab plus API, keyboard/focus, performance, accessibility, and visual regression gates.

Current Canonical Primitives

These are the defaults agents and humans should reach for before writing local JSX:

| Need | Use | | --- | --- | | Metadata keys, grouped menu labels, column groups | DisplayLabel from admin-ui-starter-kit/typography | | Displayed values, identities, contact links, money, numbers, durations, dimensions, file sizes, dates | Value, Name, Initials, MonoValue, Link, Email, Phone, Url, Money (including unit="minor" and secondary currency display), Number, Percent, FileSize, Duration, Dimensions, Date, Time, DateTime, DateRange, RelativeTime from admin-ui-starter-kit/primitives | | Plain table markup | Table, TableHeader, TableBody, TableRow, TableHead, TableCell from admin-ui-starter-kit/base/table | | Data table behavior and primary/secondary stacked cells | DataTable, CellValue, CellStack from admin-ui-starter-kit/features/table | | Overflow/page/card/table row actions | ActionMenu / ActionButtons from admin-ui-starter-kit/base/navigation | | Status chips, tags, small semantic labels | Badge from admin-ui-starter-kit/base/badge | | Icon/avatar/title/description/action rows | Item from admin-ui-starter-kit/base/item | | Empty/loading/error surfaces | LoadingState, ErrorState from admin-ui-starter-kit/base/display; EmptyState from admin-ui-starter-kit/composed/data-display | | Label/value facts | MetadataList, MetadataValue from admin-ui-starter-kit/composed/data-display | | Relationship card content scaffolds | RelationshipCardFrame from admin-ui-starter-kit/composed/data-display, framed by SmartCard at the page call site | | Card primary/body action rows | CardPrimaryAction, CardActionStrip from admin-ui-starter-kit/base/cards | | Editable label/control/error rows | FormField / ControlledFormField from admin-ui-starter-kit/base/forms | | Editable amount plus currency | MoneyInput from admin-ui-starter-kit/base/forms (optional form chrome, one { amount, currency } callback) | | Filtered table chrome | DataViewShell and features/filters beside features/table | | Row/cell/inline preview | AsyncPreview, PreviewTriggerCell from admin-ui-starter-kit/features/async-preview (data for existing payloads, onShow for lazy loading) | | Mixed comments, events, and audit logs | ActivityLog, toActivityLogEntry, createActivityEventAdapter from admin-ui-starter-kit/features/activities |

DisplayLabel is intentionally one fixed style: text-sm, foreground, medium, tight leading. Caller typography overrides are ignored so metadata labels, grouped menu labels, and column-group labels stay visually identical. Value is intentionally limited to type="main" or type="secondary": main is the default foreground display-data tone, and secondary is the muted supporting-value tone. Use Text for broader prose typography roles. For tables, use CellValue when a column displays one primitive-backed value and CellStack when a column needs primary plus secondary values such as name, email, phone, file size, date, person name, or a custom badge. CellStack skips null, undefined, and blank string values, promotes the first visible item as the normal value, renders the rest smaller/secondary by default, and caps output at four rows. For preview cells, put PreviewTriggerCell inside AsyncPreview.Trigger. It renders a button and caret only when preview interaction exists; empty, disabled, or non-previewable values keep the same row footprint without pretending to be interactive. For metadata, prefer typed MetadataValue descriptors when a value arrives as data (email, phone, url, link, date, datetime, money, badge, mono, empty) and reserve custom React nodes for genuinely custom layout. For relationship cards, the app owns the domain data and actions while RelationshipCardFrame owns the consistent region order. For activity, map raw events with toActivityLogEntry or createActivityEventAdapter before rendering ActivityLog; user-facing rows should never expose raw backend event keys.

Why This Exists

Every admin product eventually rebuilds the same layer:

  • rows with media, titles, descriptions, and actions;
  • form rows with label, control, hint, and validation state;
  • dense typography and metadata rules;
  • provider-level display defaults;
  • table, filter, empty, loading, and error states;
  • reusable action menus and overlay actions;
  • features that work in Vite, Next.js, Remix, Inertia, Tanstack Router, or any React app without importing those frameworks.

Admin UI Starter Kit centralizes that layer. Consumers wire routing, data, permissions, translations, uploads, and cache invalidation through callbacks, fetchers, accessors, render props, and strings props.

Design Contract

  1. Layered architecture. ui/ is internal shadcn output and is never edited directly. Public code flows through typography/, primitives/, base/, layout/, composed/, and features/. npm run lint:architecture enforces the boundaries.
  2. Framework-agnostic behavior. The package imports no app routers, query clients, i18n runtimes, or backend SDKs.
  3. Exact entrypoints. Import only the family you use: admin-ui-starter-kit/base/item, admin-ui-starter-kit/features/comments, etc. Broad base, composed, and features barrels are intentionally not public.
  4. Provider + tokens. <UIProvider> owns JS-side display defaults. CSS variables under src/css/tokens/ own scale, density, states, controls, rows, surfaces, media, and shell dimensions. Default surface insets use the unsuffixed p-surface family; compact and spacious insets are explicit variants, never alternate defaults.
  5. Typography is structural. Every library-owned title, description, label, helper/error message, menu label, and accessible-only string renders through Text, Heading, DisplayLabel, Label, TextLink, or a semantic display primitive. Arbitrary consumer ReactNode slots stay unwrapped.
  6. Strings are overridable. User-facing text flows through useStrings(defaults, override).
  7. Slots and render props before forks. Complex features expose partials, slots, hooks, and render props so apps can reshape without copying source.
  8. Stable DOM hooks. Public components expose {kebab-name}--component and named region classes for theming, testing, analytics, and DevTools.

Maintainer Confidence Gates

The package treats public types and presentation behavior as versioned contracts:

npm run api:check                  # exact public TypeScript API snapshot
npm run test:interaction-contracts # keyboard, Escape, and focus return
npm run test:visual:parity         # theme/density/font/text/radius matrix
npm run build:lib
npm run test:performance-budget    # CSS, entrypoint, format, and dist budgets

npm run verify:release includes these checks alongside package smoke, registered-route browser coverage, axe, feature visual baselines, and the normal architecture/token/type/lint/test gates.

What This Is Not

  • Not a public mirror of src/components/ui/*; those shadcn files are an internal maintainer layer.
  • Not a marketing-site kit; it is built for app interiors.
  • Not a backend framework; routing, data, auth, permissions, uploads, and i18n stay in the consuming app.

Install

For a full framework and bundler setup walkthrough, see INTEGRATION.md.

Migrating an app that already copied an older local UI tree? Start with MIGRATION.md, then use COMPONENT_SELECTION.md to choose exact package entrypoints.

1. Add the package

npm install admin-ui-starter-kit

2. Required peers

Always required:

npm install react react-dom tailwindcss lucide-react

lucide-react is intentionally a v1 peer. Apps still on lucide v0 should plan that icon-package upgrade as part of adoption.

3. Optional peers — install only what you use

| Feature you import | Install peer | | --- | --- | | features/rich-text-editor | @tiptap/pm @tiptap/react @tiptap/starter-kit | | base/map | leaflet leaflet-draw leaflet.fullscreen leaflet.markercluster react-leaflet react-leaflet-markercluster | | base/carousel | embla-carousel-react | | base/display/chart | recharts | | composed/analytics (charts) | recharts | | features/data-view, features/products, features/table | @tanstack/react-table | | base/forms sortable repeaters/media gallery; features/kanban | @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities | | base/date-pickers; date facets in features/filters | react-day-picker@^10 | | base/display/qr-code | qrcode culori | | RHF-backed exports in base/forms; features/sync; features/rich-text-editor | react-hook-form | | base/toaster, base/copyable, composed/admin | sonner | | base/command, base/combobox, base/popover-menu; features/filters, features/global-search, features/mentions, features/suggestions | cmdk |

Skip the ones you don't import — npm will warn but not fail. Date picker calendarProps are forwarded to DayPicker v10, so use v10 prop names (startMonth / endMonth) rather than deprecated v9 names. The kit-level yearSelection.fromYear / toYear option remains supported and is mapped internally.

4. Mount the stylesheet

// in your root layout
import 'admin-ui-starter-kit/style.css';

The shipped stylesheet is a pre-compiled Tailwind v4 build (~360 KB minified). If you already run Tailwind in your app, see the Build your own section below to skip the pre-built CSS.

5. Mount the provider (optional but recommended)

// app.tsx
import { UIProvider } from 'admin-ui-starter-kit/ui-provider';

<UIProvider config={{
  money: { defaultCurrency: 'USD', locale: 'en-US' },
  dates: { weekStartsOn: 1, format: 'dd MMM yyyy' },
  forms: { defaultControlSize: 'default', defaultLabelSize: 'default' },
  table: { defaultSize: 'small' },
  button: { defaultVariant: 'primary', defaultButtonStyle: 'solid' },
  badge: { defaultVariant: 'primary', defaultSize: 'xs' },
  item:  { defaultSize: 'default' },
  card:  { defaultSurface: 'framed', defaultTitleSize: 'small' },
  toast: { position: 'bottom-center', theme: 'dark' },
}}>
  <App />
</UIProvider>

The provider is locked at first mount; pass everything once at the root. For sizing and spacing, prefer the calibrated package scale (default / small) plus structural CSS variables such as --height-control, --height-action, --control-x, --row-y, --surface-x, and --space-scale over repeated per-component size props.

Structural scale is intentionally small. Tune typography with direct text tokens such as --text-xs, --text-pxs, --text-sm, --text-base, and their paired --text-*--line-height values when a theme needs exact sizing. Tune global UI shape with --space-scale and --density-scale first; use the public --space-*, --control-*, --row-*, --surface-*, --icon*, --avatar*, and shell variables when a product needs a precise override. Do not recreate old component-family aliases such as action, card, table, header, or app-shell spacing tokens in consuming apps. Buttons and clickable triggers consume the action ladder (--height-action-*); inputs, selects, and editable fields consume the control ladder (--height-control-*). Their defaults align by design, but the semantic families are independent so a scoped product theme can resize actions without silently resizing form controls. Every public package-owned root token is documented beside its declaration, and npm run lint:tokens rejects undocumented tokens or raw interactive geometry that bypasses these families. The surface ladder is semantic: default cards, dialogs, sheets, panels, and content/card/multi-section popovers use p-surface (or the matching px-surface / py-surface split). Item/option/action collections use the 0.5rem p-surface-xs inset—on the outer shell for pure menus and on the list region for mixed pickers with separately padded chrome. Explicit compact surfaces may also use *-surface-xs; *-surface-lg is the explicit spacious tier. *-surface-sm is a deprecated compatibility spelling, not a package-source or new-consumer default. Visual theme values stay in CSS, not in <UIProvider>. Override --link-color for link-like inline values, --success / --info / --warning for package semantic states, and --inverse-* for explicit dark or inverse toast surfaces from the theme/defaults layer. --success and --info are independent semantic state colors, not aliases to primary or chart colors; their solid fills use the paired foreground token for WCAG AA contrast.

6. Optional action runtime

Use services/actions when the same action should appear in several places (page header, table row menu, card menu, command palette) and share one loading/error/overlay lifecycle. Keep one-off dialogs local with features/overlays.

import {
  ActionOverlayOutlet,
  ActionProvider,
  useLocalAction,
  type ActionRequestRunner,
  type ActionRuntimeGlue,
} from 'admin-ui-starter-kit/services/actions';

const requestRunner: ActionRequestRunner = async (request) => {
  const method = request.method;
  const target = request.target;

  if (target?.type === 'named') {
    return appVisit(appRoute(target.name, target.params), {
      ...request.options,
      method,
      data: request.data,
      headers: request.headers,
    });
  }

  if (target?.type === 'url') {
    return appHttp(target.url, {
      ...request.options,
      method,
      data: request.data,
      headers: request.headers,
    });
  }

  throw new Error('Action request target is missing.');
};

const actionRuntime: ActionRuntimeGlue = {
  requestRunner,
  feedback: {
      onSuccess: (event) => event.message && toast.success(event.message),
      onError: (event) => event.message && toast.error(event.message),
  },
  guards: {
    can: (permission) => auth.can(permission),
    hasAnyRole: (roles) => auth.hasAnyRole(roles),
  },
  parseErrors: (error) => {
    const response = error as {
      errors?: Record<string, string | string[]>;
      message?: string;
    };

    return {
      fieldErrors: response.errors,
      message: response.message ?? 'Action failed.',
    };
  },
  onOverlayOpenChange: (open) => polling.setPaused(open),
  onRunningChange: (running) => activity.setBusy(running),
};

<ActionProvider runtime={actionRuntime}>
  <App />
  <ActionOverlayOutlet />
</ActionProvider>

Actions can be callback-driven with run, or data-driven through request plus a provider/action requestRunner. Provider guards keep permission and role checks app-owned, while parseErrors normalizes backend validation payloads into action.errors and failed lifecycle messages. Use useLocalAction for incremental page-owned migrations, and use <ActionScope> + useActionSurface when one definition needs to power page, table, card, command, or overlay surfaces.

const confirmBooking = useLocalAction<Booking, { note: string }, Booking>({
  id: 'booking.confirm',
  label: 'Confirm booking',
  permission: 'bookings.confirm',
  modality: {
    type: 'dialog',
    title: ({ payload }) => `Confirm ${payload?.reference}`,
    formId: 'confirm-booking-form',
    render: ({ getFormProps, isRunning }) => (
      <form {...getFormProps()} id="confirm-booking-form">
        <textarea name="note" disabled={isRunning} />
      </form>
    ),
  },
  request: {
    method: 'post',
    target: ({ payload }) => ({
      type: 'named',
      name: 'bookings.confirm',
      params: payload?.id,
    }),
    data: ({ values }) => values,
  }}
}, { payload: booking });

Pages register contextual actions with useRegisterActions or <ActionScope>. Routing, permissions, translations, cache invalidation, and toasts stay in the consuming app through callbacks, provider adapters, and lifecycle hooks. Resolved actions expose status, isRunning, isSuccess, isError, errors, message, result, and timestamps; overlay form actions can use modality.formId with getFormProps(...) in the render context.

7. Install the AI agent skills (optional)

This package ships AI skills for Claude Code and other agents:

  • component-library-rules — maintainer rules for editing this library.
  • admin-ui-components — component selection, layer, and reference workflow.
  • token-system — shadcn theme + structural token workflow.
  • admin-ui-consumer-migration — consumer-app migration rules and guardrails.
  • component-quality-pass — route, visual, API, token, and test audit workflow.

After installing the package:

npx admin-ui-starter-kit-install-skill

Copies the skills into your project's .claude/skills/ and .agents/skills/. Run again to update after a package upgrade.

Flags:

  • --skill=all|component-library-rules|admin-ui-components|token-system|admin-ui-consumer-migration|component-quality-pass (default all)
  • --target=claude|agents|both (default both) — pick which directory to install into.
  • --force — overwrite without prompting.

For migration checks in a consuming app:

npx admin-ui-starter-kit-audit
npx admin-ui-starter-kit-audit --fix

The audit rejects stale copied imports, invalid package subpaths, and local files that only re-export package components.


Browse the component showcase

Every component shipped with this package has a docs page (preview + code tab + props table). To browse the full showcase locally:

npx admin-ui-starter-kit-showcase

Opens http://127.0.0.1:7654 with sidebar navigation across UI / Base / Composed / Features / Layout, search, and per-component examples + APIs.

For agent or text-first discovery, search .agents/skills/component-library-rules/references/components/INDEX.json. Each entry exposes the route, status, primary exact publicImport, documented components in api, example names, and a doc path. Open only the matching doc: its generated Public API section contains exact named imports and props, followed by consumer-normalized examples. Empty API metadata on ui/** pages is intentional because raw shadcn primitives are private.

Auth layouts live under the exact layout subpath:

import { AuthShell } from 'admin-ui-starter-kit/layout/auth';

Use them for login, register, invite, reset, magic-link, verification, and two-factor page chrome. The shell covers logo/brand, social provider buttons, policy links, language links or switchers, and full-bleed top media strips. Forms, submit handlers, translations, locale state, and framework links stay in the consuming app.

Media picking and product gallery flows live under the exact feature subpath:

import { MediaLibrary, MediaLibraryDialog } from 'admin-ui-starter-kit/features/media-library';
import { SchemaForm } from 'admin-ui-starter-kit/features/schema-form';

Use MediaLibrary for embedded asset management and MediaLibraryDialog when the picker should open in the shared overlay Dialog. Fetching, uploads, metadata edits, storage URLs, and framework routing stay in the consuming app through direct props.

Use SchemaForm for backend-provided settings, tenant configuration, integration setup, or other admin schemas that should render as one cohesive form or as section cards. Keep validation libraries, server errors, route changes, translations, async option fetching, and app-specific controls in the consuming app through direct props.

Flags:

  • --port <n> — pick a port (default 7654)
  • --host <h> — bind to a different host (default 127.0.0.1)
  • --no-open — do not auto-open a browser

The bundle is shipped pre-built inside the package; no build step required.


Architecture

Layer order

ui/        → shadcn primitives (DO NOT EDIT)
typography/→ Text, Heading, DisplayLabel, Label, TextLink
primitives/→ Value, Name, Initials, Email, Phone, Money, Number, FileSize, Duration, Dimensions, Date, DateRange, RelativeTime
base/      → typography-aware wrappers (Button, Item, FormField, ...)
composed/  → domain rows + cards (TeamMemberRow, ContactCard, MetricGrid, ...)
features/  → provider-driven feature surfaces (Comments, Filters, Overlays, ...)
layout/    → app-shell scaffolds (PageHeader, Sidebar, ...)

Each layer can only import from the layer below it — lint:architecture enforces this on every PR.

Three pillars

<UIProvider> — library-wide defaults. One zustand store, slice-scoped subscriptions, locked-once at boot:

const { defaultCurrency } = useMoneyConfig();
const currency = props.currency ?? defaultCurrency;

useStrings() — overridable internal copy. Every component owns a <Name>Strings interface + default<Name>Strings:

<ContactCard strings={{ contact: t('actions.contact') }} />

<Item>, <MetadataList>, and <FormField> — canonical row decisions. Any "media + title + description + actions" row goes through Item. Any label/value fact summary goes through MetadataList. Any editable "label + control + error" row goes through FormField/ControlledFormField.

<Item>
  <ItemMedia variant="avatar"><Avatar src={user.avatar} /></ItemMedia>
  <ItemContent>
    <ItemTitle>{user.name}</ItemTitle>
    <ItemDescription>{user.role}</ItemDescription>
  </ItemContent>
  <ItemActions><Badge variant="success">Active</Badge></ItemActions>
</Item>

<ControlledFormField name="email" control={control} label="Email" required>
  {(field, _err, invalid) => (
    <Input value={field.value} onChange={field.onChange} invalid={invalid} />
  )}
</ControlledFormField>

Entry-points

Import per component family. This keeps optional peers isolated: importing base/item should not require react-day-picker, and importing features/overlays should not require TipTap.

import { Text } from 'admin-ui-starter-kit/typography';
import { Button } from 'admin-ui-starter-kit/base/buttons';
import { SmartCard } from 'admin-ui-starter-kit/base/cards';
import { Item, ItemGroup, ItemMedia, ItemContent, ItemTitle } from 'admin-ui-starter-kit/base/item';
import { ControlledFormField, FormSection, Input, PasswordInput } from 'admin-ui-starter-kit/base/forms';
import { DataViewShell, DataViewToolbar } from 'admin-ui-starter-kit/features/data-view';
import { AsyncResourceCombobox, EnhancedCombobox } from 'admin-ui-starter-kit/base/combobox';
import { DataTable, ResourceCell, StatusClusterCell } from 'admin-ui-starter-kit/features/table';
import { Badge } from 'admin-ui-starter-kit/base/badge';
import { Alert } from 'admin-ui-starter-kit/base/display';
import { Carousel, CarouselContent, CarouselItem } from 'admin-ui-starter-kit/base/carousel';
import { ChartContainer } from 'admin-ui-starter-kit/base/display/chart';
import { ActionList, ApiTokenManager } from 'admin-ui-starter-kit/composed/admin';
import { ContactCard } from 'admin-ui-starter-kit/composed/cards';
import { BookingCalendarEventCard, SeoListingCard } from 'admin-ui-starter-kit/composed/commerce';
import { RelationshipCardFrame } from 'admin-ui-starter-kit/composed/data-display';
import { ChartCard } from 'admin-ui-starter-kit/composed/analytics';
import { Comments } from 'admin-ui-starter-kit/features/comments';
import { SharedResourceCard } from 'admin-ui-starter-kit/features/resource-assignment';
import { parseFiltersFromQuery, serializeFiltersToQuery } from 'admin-ui-starter-kit/features/filters';
import { ProductVariantsManager } from 'admin-ui-starter-kit/features/products';
import { Container, Section } from 'admin-ui-starter-kit/layout/containers';
import { Dialog, Drawer } from 'admin-ui-starter-kit/features/overlays';
import { SchemaForm } from 'admin-ui-starter-kit/features/schema-form';
import { HeaderToolButton, HeaderToolPopover } from 'admin-ui-starter-kit/layout/header';
import { ResourceIndexShell, ResourceShowShell, TabbedResourceShell } from 'admin-ui-starter-kit/features/resource';
import { SettingsShell } from 'admin-ui-starter-kit/layout/settings';
import { ActionProvider, ActionOverlayOutlet } from 'admin-ui-starter-kit/services/actions';
import { useStrings } from 'admin-ui-starter-kit/lib/strings';

Use documented exact entrypoints only. MetadataList has its own public subfamily entrypoint because it is often used on its own. Chart primitives also have an isolated entrypoint so consumers do not pull Recharts through unrelated display helpers:

import { MetadataList } from 'admin-ui-starter-kit/composed/data-display';
import { ChartContainer } from 'admin-ui-starter-kit/base/display/chart';

Safe public entrypoint shapes:

  • admin-ui-starter-kit — provider, strings helpers, and cn only.
  • admin-ui-starter-kit/ui-provider
  • admin-ui-starter-kit/typography
  • admin-ui-starter-kit/base/buttons, admin-ui-starter-kit/base/item, etc.
  • admin-ui-starter-kit/features/data-view
  • admin-ui-starter-kit/base/table
  • admin-ui-starter-kit/features/table
  • admin-ui-starter-kit/base/combobox
  • admin-ui-starter-kit/composed/data-display
  • admin-ui-starter-kit/base/display/chart
  • admin-ui-starter-kit/composed/cards, admin-ui-starter-kit/composed/analytics, admin-ui-starter-kit/composed/admin, admin-ui-starter-kit/composed/commerce, etc.
  • admin-ui-starter-kit/features/comments, admin-ui-starter-kit/features/filters, admin-ui-starter-kit/features/schema-form, etc.
  • admin-ui-starter-kit/features/products
  • admin-ui-starter-kit/layout and exact layout families such as admin-ui-starter-kit/layout/page and admin-ui-starter-kit/layout/settings
  • admin-ui-starter-kit/features/resource for state-aware resource workspaces, not ordinary page chrome
  • admin-ui-starter-kit/services/actions
  • admin-ui-starter-kit/lib/strings
  • admin-ui-starter-kit/lib/utils

Raw shadcn primitives under src/components/ui/* are intentionally not public package exports. If a consumer needs Tabs, import the library wrapper surface that owns that pattern, such as admin-ui-starter-kit/base/navigation, rather than admin-ui-starter-kit/ui/tabs.

Avoid broad component imports such as admin-ui-starter-kit/base, admin-ui-starter-kit/composed, or admin-ui-starter-kit/features; they are intentionally not exported. Use the exact family subpath so missing optional peers fail only when you import the feature that needs them. The package exports map lists each public family as an exact key; wildcard exports and null wildcard blockers are intentionally avoided because bundlers handle them inconsistently.

Internal shadcn primitive inventory

Maintainers can add shadcn primitives with npx shadcn@latest add <primitive>, which writes to src/components/ui/. Those files are reproducible shadcn output and should be wrapped in base/, layout/, or features/ before becoming a public package API.

Installed internal primitives include: accordion, alert-dialog, alert, aspect-ratio, avatar, badge, breadcrumb, button, button-group, calendar, card, carousel, chart, checkbox, collapsible, combobox, command, context-menu, dialog, direction, drawer, dropdown-menu, empty, field, hover-card, input, input-group, input-otp, item, kbd, label, menubar, native-select, navigation-menu, pagination, popover, progress, radio-group, resizable, scroll-area, select, separator, sheet, sidebar, skeleton, slider, slot, sonner, spinner, switch, table, tabs, textarea, toggle, toggle-group, and tooltip.

Dropdown menus use Base UI's render prop for custom triggers, not Radix's asChild prop:

import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from 'admin-ui-starter-kit/base/navigation';

<DropdownMenu>
  <DropdownMenuTrigger render={(props) => <Button {...props}>Open</Button>} />
  <DropdownMenuContent />
</DropdownMenu>

For repeated overflow actions, prefer ActionMenu with an actions definition array from the same base/navigation entrypoint. Use ActionButtons when the same definitions need to render inline buttons. Data table row actions, SmartCard actions, and table column controls share that shell. Reach for low-level DropdownMenu parts only when the menu needs radio, submenu, or bespoke content. The shared dropdown surface inherits the active theme; only use inverse styling when the surrounding product surface is explicitly inverse. Fix row padding and item density in base/navigation/dropdown-menu, not with caller-specific overrides.

For card-owned action rows, use CardPrimaryAction for the single app-primary body action and CardActionStrip for repeated body/footer actions. Both live in admin-ui-starter-kit/base/cards and render through the centralized button and action definitions.

Reusable composed/ and features/ components are card-free content blocks by default. Frame display-only relationships with SmartCard and RelationshipCardFrame at the page, drawer, dialog, or expansion call site:

import { SmartCard } from 'admin-ui-starter-kit/base/cards';
import { RelationshipCardFrame } from 'admin-ui-starter-kit/composed/data-display';

<SmartCard title="Owner">
  <RelationshipCardFrame title={owner.name} metadata={ownerMetadata} />
</SmartCard>

For module-owned relationship cards, compose RelationshipCardFrame inside that SmartCard. The app passes title, badge, selector, metadata, primary action, overflow action, and details slots; the package owns the spacing and region order.

For stateful SharedResourceCard assignment flows, keep suggestion shapes app-owned. When a suggestion uses name, title, or another field instead of label, pass selector.getSelectionLabel so the package default selection summary can render the chosen resource without assuming the consumer domain model. resource is the only selected/empty source of truth; pass null when the relationship is unassigned and omit the deprecated hasResource flag. Supply viewAction when navigation must render through the consumer's router.

Do not nest a second card inside an already framed section just because a component name contains Card.


Build your own stylesheet

The pre-built style.css is the recommended default. To publish editable local copies of the package theme/default token layers:

npx admin-ui-starter-kit-publish-theme

This writes src/styles/admin-ui/theme.css, defaults.css, overrides.css, admin-ui.css, and source.css. theme.css is the swappable shadcn adapter; defaults.css is a flattened editable copy of the package token layers. Import src/styles/admin-ui/admin-ui.css when you want package CSS plus checked-in token overrides.

If you intentionally skip the pre-built CSS and rebuild Tailwind in your app:

  1. Don't import admin-ui-starter-kit/style.css.

  2. Add the kit's source to your Tailwind content:

    // tailwind config (or @source in your CSS)
    content: ['./node_modules/admin-ui-starter-kit/dist/**/*.{js,cjs}'],
  3. Start from the generated src/styles/admin-ui/source.css. Keep shadcn color, radius, shadow, dark/light, chart, and sidebar values in the theme layer; keep package states, --link-color, inverse tokens, structural typography, spacing, density, action/control heights, rows, surfaces, media, shell, and component knobs in the defaults layer.


CommonJS support

Both ESM (.js) and CJS (.cjs) are emitted. The exports map resolves automatically:

// ESM
import { Item } from 'admin-ui-starter-kit/base/item';

// CJS
const { Item } = require('admin-ui-starter-kit/base/item');

For maintainers

This package is governed by a single rules file: .agents/skills/component-library-rules/SKILL.md. It is the contract — everything from the layer order to typography to the strings pattern to the Item/FormField primitives is documented there.

Companion docs:

  • AGENTS.md — what AI agents should read first.
  • CLAUDE.md — Claude Code entry point.
  • references/ — per-task deep guides (new feature, strings, ui-provider, base wrapper, preview pages, item pattern, form-field pattern, visual evaluation).

Common scripts

npm run dev              # start the preview app
npm run build:lib        # compile the publishable artifact (dist/)
npm run typecheck        # tsc --noEmit
npm run lint             # eslint
npm run lint:architecture # check layer-import boundaries
npm run test:exports     # check the exports map matches the file tree
npm run api:check        # detect public TypeScript API drift
npm run test:dist-assets # check style.css referenced assets exist after build:lib
npm run test:performance-budget # enforce CSS, entrypoint, format, and dist budgets
npm run test:package-smoke # pack + temporary consumer build for exact subpaths
npm run test:browser:features # Playwright feature route/workflow sweep
npm run test:interaction-contracts # keyboard, Escape, and focus return
npm run test:a11y:features # axe pass over feature showcase routes
npm run test:visual:features # committed feature screenshot baselines
npm run test:visual:parity # light/dark, density, font, text, and radius matrix
npm run test             # vitest run
npm run verify           # architecture + tokens + smells + exports + docs + typecheck + lint + tests
npm run verify:release   # full maintainer release gate
npm run publish:skill    # copy repo skills into ~/.claude/skills and ~/.agents/skills

The browser gate is registry-driven rather than a hand-maintained route list. At both desktop and mobile widths it visits every ready showcase route, scrolls through the full page, exercises targeted workflows, and validates each .example--component for render errors, collapsed geometry, header collisions, minimum readable text, and contained horizontal overflow. Accessibility and visual commands use the same canonical feature-route registry; committed desktop/mobile baselines must map one-to-one to those routes. The focused parity suite uses #/docs/parity-lab instead of multiplying every route across every presentation scenario.

Project layout

src/
├── components/
│   ├── ui/         shadcn primitives (do not edit)
│   ├── typography/ Text, Heading, DisplayLabel, Label, TextLink
│   ├── primitives/ Value, Name, Initials, Link, Email, Phone, Url, Money, Number, FileSize, Duration, Dimensions, Date, DateRange
│   ├── base/       typography-aware wrappers + base/item + base/forms/form-field
│   ├── composed/   domain rows + cards (admin, cards, commerce, dark-surfaces, …)
│   ├── features/   feature surfaces (activities, comments, filters, overlays, …)
│   └── layout/     app-shell scaffolds
├── lib/
│   ├── ui-provider/  zustand store + slice hooks
│   ├── strings.ts    useStrings + StringsProp + DeepPartial helpers
│   └── utils.ts      cn() etc.
├── hooks/          cross-cutting hooks (useDebounce, useMobile, …)
└── preview/        in-repo showcase app source (excluded from publish; dist/showcase ships prebuilt)

License

MIT © Nicolas Vlachos. See LICENSE.